天空宫阙 发表于 2023-6-23 19:49

常见编码和加密(python和JavaScript使用示例)

# 常见编码和加密



## 编码
ASCII码

Base64

Hex

> Base64 Hex 对二进制数据的一种编码方式,不属于加密,但加密后一般需要对数据进行编码Base64 Hex比较常见。

## 信息摘要(哈希)
md5
```
import hashlib
m = hashlib.md5()
m.update('hello world'.encode('utf-8'))
print(m.hexdigest())
```
```
const CryptoJS = require("crypto-js");
console.log(CryptoJS.MD5('hello world').toString())
```

SHA1
SHA256
> SHA1 SHA256 也可分别使用python的hashlib库和JavaScript的crypto-js实现,使用方法类似

## 加密

AES加密

DES加密

| 算法 | 密钥长度    | 工作模式         | 填充模式                            |
| ---- | ----------- | ---------------- | ----------------------------------- |
| AES| 56/64       | ECB/CBC/PCBC/CTR | NoPadding/ZeroPadding/PKCS5Padding/ |
| DES| 128/192/256 | ECB/CBC/PCBC/CTR | NoPadding/PKCS5Padding/PKCS7Padding |

python和javascript使用AES/CBC/PKCS7的代码如下

```python
from Crypto.Cipher import AES# pip install pycryptodome
import base64
import binascii

def encrypt(k, iv, content):
   # k:密钥,iv:偏移量,content:需加密的内容
   k = k.encode("utf-8")
   iv = iv.encode("utf-8")
   # pad = lambda s: s + (16 - len(s)%16) * chr(0) # AES加密时,明文长度需为16的倍数。这里的pad用来填充,chr(0)表示为ZeroPadding,在最后填充0直到长度为16的倍数
   pad = lambda s: s + (16 - len(s)%16) * chr(16 - len(s)%16) # 这里为Pkcs7填充
   content = pad(content).encode("utf-8")
   cipher = AES.new(k, AES.MODE_CBC, iv)# CBC模式加密,还有ECB模式
   cipher_text = cipher.encrypt(content)
   # enc = base64.b64encode(cipher_text).decode("utf-8")
   enc = binascii.b2a_hex(cipher_text).decode("utf-8")
   return enc
   
   
k = "1234567890abcDEF"
iv = "1234567890abcDEF"
print(encrypt(k, iv, "hello world"))
```



```javascript
const CryptoJS = require("crypto-js");


var k = CryptoJS.enc.Utf8.parse('1234567890abcDEF'); // 密钥
var iv = CryptoJS.enc.Utf8.parse('1234567890abcDEF'); // iv

var encrypted = CryptoJS.AES.encrypt("hello world", k, {
    mode: CryptoJS.mode.CBC,
    iv: iv,
    padding: CryptoJS.pad.Pkcs7, // Pkcs7填充
}).toString()
console.log('base64',encrypted)
var e64 = CryptoJS.enc.Base64.parse(encrypted);
var eHex = e64.toString(CryptoJS.enc.Hex);
console.log('hex',eHex)
```

> AES的其他模式和填充方式使用方法类似
>
> ECB模式无iv值



AES对密钥的长度有限制,可以为**16字节,24字节或者32字节**,有时用户输入的密钥不满足长度的要求,crypto-js库的默认做法是根据输入密钥和随机数,利用md5生成新密钥和iv值

```javascript
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");

var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
```

以上crypto-js库默认AES加密的python实现

```python
import base64
from Crypto.Cipher import AES
from Crypto import Random
from hashlib import md5


"""
pip install pycryptodome
"""

def pad(s):
    return s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()

def unpad(s):
    return s)]

def bytes_to_key(data, salt, output=48):
    assert len(salt) == 8, len(salt)
    data += salt
    key = md5(data).digest()
    final_key = key
    while len(final_key) < output:
      key = md5(key + data).digest()
      final_key += key
    return final_key[:output]

def encrypt(data, passphrase):
    salt = Random.new().read(8)
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv
    aes = AES.new(key, AES.MODE_CBC, iv)
    cipherbyte = base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(data)))
    return cipherbyte

def decrypt(data, passphrase):
    data = base64.b64decode(data)
    assert data[:8] == b'Salted__'
    salt = data
    key_iv = bytes_to_key(passphrase, salt, 32+16)
    key = key_iv[:32]
    iv = key_iv
    aes = AES.new(key, AES.MODE_CBC, iv)
    plainbyte = unpad(aes.decrypt(data))
    return plainbyte

if __name__ == '__main__':
    data = "Message".encode()
    passphrase = "Secret Passphrase".encode()
    encrypt_data = encrypt(data, passphrase)
    print(encrypt_data)
```



RSA加密

| 填充方式               | 待填充长度             | 填充后长度 |
| ---------------------- | ---------------------- | ---------- |
| RSA_NO_PADDING         | 不填充               | 和公钥等长 |
| RSA_PKCS1_PADDING      | 至少RSA_size(rsa) - 11 | 和公钥等长 |
| RSA_PKCS1_OAEP_PADDING | RSA_size(rsa) - 41   | 和公钥等长 |



RSA_PKCS1_PADDING 相同明文每次加密结果不同

```python
import rsa
import binascii

# 生成RSA公钥和私钥
(publickey, privkey) = rsa.newkeys(1024)
# rsa使用的是RSA_PKCS1_PADDING
m = rsa.encrypt('hello world'.encode(), publickey)
encrypted_data = m.hex()
print(encrypted_data)
decrypted_data = rsa.decrypt(binascii.a2b_hex(encrypted_data) ,privkey)
print(decrypted_data)
```



RSA_NO_PADDING 相同明文每次加密结果相同

```python
import rsa


class Encrypt():
    """
    该类为参考的这篇文章:https://www.52pojie.cn/forum.php?mod=viewthread&tid=874374
    主要用于对应JS中的RSAKeyPair的RSA加密,每次对相同明文加密后的密文都是相同的,原因为NoPadding
    """
    def __init__(self, e, n):
      self.e = e
      self.n = n

    def encrypt(self, message):
      ee = int(self.e, 16)
      nn = int(self.n, 16)
      rsa_pubkey = rsa.PublicKey(e=ee, n=nn)
      crypto = self._encrypt(message.encode(), rsa_pubkey)
      return crypto.hex()

    def _pad_for_encryption(self, message, target_length):
      message = message[::-1]
      max_msglength = target_length - 11
      msglength = len(message)

      padding = b''
      padding_length = target_length - msglength - 3

      for i in range(padding_length):
            padding += b'\x00'

      return b''.join()

    def _encrypt(self, message, pub_key):
      keylength = rsa.common.byte_size(pub_key.n)
      padded = self._pad_for_encryption(message, keylength)

      payload = rsa.transform.bytes2int(padded)
      encrypted = rsa.core.encrypt_int(payload, pub_key.e, pub_key.n)
      block = rsa.transform.int2bytes(encrypted, keylength)

      return block


def USE_RSA(message):
    e = "010001"
    n = "81eb7e93bc0e458f67ad208ffb8039a139dffab9b576309212b6c5b411c0f32266ab8700c617562f294abda31f1195163a058637dfebeb80fa0b8cfa0ef63fac3a52b8c3ecf26cb334745d4c850cb5c7fd0bac81ce39e8814c7284fa8bc9721d66189fe5c6d7a3b1046cfdecc4e2976238ccc0a8a6ebbf0ebdedf4737d2dfc99"
    en = Encrypt(e, n)
    return en.encrypt(message)

ciphertext = USE_RSA("111111")
print(ciphertext)
```

RSA 密钥的参数和导入方式

RSA 密钥的参数

1. 公钥参数:
   - `n`:模数(modulus),通常为两个大素数的乘积。
   - `e`:指数(exponent),用于加密操作。
2. 私钥参数:
   - `n`:模数(modulus),与公钥中的模数相同。
   - `d`:私钥指数(private exponent),用于解密操作。

python中RSA 密钥导入方式可以使用`RSA.import_key(private_key_str)`也可以使用`RSA.construct((private_key.n,private_key.e,private_key.d))`,本质上是一样的可以根据方便程度选择。

```python
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
import binascii

sentinel = get_random_bytes(16)

private_key_str = """-----BEGIN RSA PRIVATE KEY-----
MIICXwIBAAKBgQCB636TvA5Fj2etII/7gDmhOd/6ubV2MJIStsW0EcDzImarhwDG
F1YvKUq9ox8RlRY6BYY33+vrgPoLjPoO9j+sOlK4w+zybLM0dF1MhQy1x/0LrIHO
OeiBTHKE+ovJch1mGJ/lxtejsQRs/ezE4pdiOMzAqKbrvw697fRzfS38mQIDAQAB
AoGAYWca+Muur3v6MJQPHnFdw4BOaf09DKURfrJEuuHslNwfuU13yQvJ84WzoUVg
j6AEj++AVvesOl3yGSLR+OIBnqMFZvs6MjBzn5RdKjD3PiiBdUgBh+bGA+9peeqo
8E0FBiPLkedXorM1YtR39cxPPhreNO/R+ApMLA/Z3RZDeWECRQCypZ9xrqWTMYDg
imfyQbKyX2F1ow/fyC29G/ysuT/x8TyiJwWR0QjKEYA1H1G+jEZALJj2H/N9rUFk
ad6ZmN5lPfBCDQI9ALospps+/QyVQl6yIp13XskhjGOhhyzmEOlkeOJbDcy+eLey
ND8jj32YhNMXe29r/InC6msuuLIo9ujdvQJEMzg1PLzcEBWzY62LG/QmLeoW4Ul9
NaYJJx0tFsCOSunlfoA9oo8SPA1Eevad00oYojGnMXn7r97KzuVjwxoHOXPGvMkC
PCxmpb10wkkT9+Y5ucOwSmzRkXfZeDGfFP10ttfVO29PJd85ovhD9N7RVyw493lV
Wb9JOzsgw2/KEUjsSQJEWrAxPbERzSsM+syfrcOhs424v4m97v10kdYXcpuWuAuy
v8yozU2CM1cFEg3CxNJEw1dF9bHon2LJtz2W6gjDLtm9y4I=
-----END RSA PRIVATE KEY-----"""
# 私钥导入方式1
private_key = RSA.import_key(private_key_str)
print('数模n(16进制)',hex(private_key.n))
print('指数e(16进制)',hex(private_key.e))
print('私钥指数private exponent d(16进制)',hex(private_key.d))

# 私钥导入方式2
private_key2 = RSA.construct((private_key.n,private_key.e,private_key.d))
cipher_rsa = PKCS1_v1_5.new(private_key2)
# 密文
encrypted_data = '1d8a3e73f4179e20a9328fe17551d99aa92e0a510c382dec4e03f0d0f4d38e81580748fcce4c6c1361b65ca8010e9eaa8d274b2def2390e00b3b5748da099d3376cf9a43845c52b1f91c4b6a40caf40bc26ef8e8fa98e226afdcca8b8304f8cb4c6e8e336436f0f809f70b65c7f9acd0d7aab25d7b9de89458c9622e6f815477'
decrypted_data = cipher_rsa.decrypt(binascii.a2b_hex(encrypted_data),sentinel=sentinel)
print(decrypted_data)


```



> 参考链接
> https://github.com/DingZaiHub/PythonSpider/tree/master/00-%E9%80%9A%E7%94%A8%E5%8A%A0%E5%AF%86
> https://www.bilibili.com/video/BV16e4y1G7xv?p=27

Arctic7 发表于 2023-6-24 15:58

自己在公司内源项目写了完整的JS加解密方法,包括AES-CCB AES-GCM,ECC,SHA256等等,cryptojs只能解决一部分问题,最核心的随机数生成还是用的nodejs原生的crypto模块

天空宫阙 发表于 2023-6-25 18:41

Arctic7 发表于 2023-6-24 15:58
自己在公司内源项目写了完整的JS加解密方法,包括AES-CCB AES-GCM,ECC,SHA256等等,cryptojs只能解决一部 ...

公司做什么的?爬虫还是nodejs的后端,前端的加密要省事一般用cryptojs,要安全性高一般要魔改加密并添加混淆了

52Mxw 发表于 2023-6-23 20:36

用心了,,,

yjn866y 发表于 2023-6-23 21:58

必须一加分

redfieldw 发表于 2023-6-23 22:04

感谢科普,收藏

d5850951556 发表于 2023-6-23 22:26

看着这些代码,头疼……

moruye 发表于 2023-6-23 23:21

89684828 发表于 2023-6-24 07:27

谢谢楼主,期待更好的作品!

jinzhu160 发表于 2023-6-24 08:19

zhengsg5 发表于 2023-6-24 10:53

非常不错,多谢分享!
页: [1] 2
查看完整版本: 常见编码和加密(python和JavaScript使用示例)