【问题标题】:CryptoJS and Pycrypto working togetherCryptoJS 和 Pycrypto 一起工作
【发布时间】:2012-07-19 18:42:58
【问题描述】:

我正在使用 CryptoJS (v 2.3) 对 Web 应用程序中的字符串进行加密,并且我需要在服务器上使用 Python 对其进行解密,因此我使用的是 PyCrypto。我觉得我错过了一些东西,因为我无法让它工作。

这是 JS:

Crypto.AES.encrypt('1234567890123456', '1234567890123456',
                   {mode: new Crypto.mode.CBC(Crypto.pad.ZeroPadding)})
// output: "wRbCMWcWbDTmgXKCjQ3Pd//aRasZ4mQr57DgTfIvRYE="

蟒蛇:

from Crypto.Cipher import AES
import base64
decryptor = AES.new('1234567890123456', AES.MODE_CBC)
decryptor.decrypt(base64.b64decode("wRbCMWcWbDTmgXKCjQ3Pd//aRasZ4mQr57DgTfIvRYE="))
# output: '\xd0\xc2\x1ew\xbb\xf1\xf2\x9a\xb9\xb6\xdc\x15l\xe7\xf3\xfa\xed\xe4\xf5j\x826\xde(m\xdf\xdc_\x9e\xd3\xb1'

【问题讨论】:

  • 如果我错了,请纠正我,但我在你的 python 代码中看不到任何填充

标签: javascript python encryption pycrypto cryptojs


【解决方案1】:

这是 CryptoJS 3.1.2 的版本。始终注意以下事项(在两种语言中使用相同):

  • 操作模式(本例中为 CBC)
  • 填充(在这种情况下为零填充;最好使用 PKCS#7 填充)
  • 密钥(相同的推导函数或清除密钥)
  • 编码(密钥、明文、密文等的编码相同)
  • IV(加密期间生成,用于解密)

如果将字符串作为 key 参数传递给 CryptoJS encrypt() 函数,则该字符串用于派生用于加密的实际密钥。如果您希望使用密钥(有效大小为 16、24 和 32 字节),则需要将其作为 WordArray 传递。

CryptoJS 加密的结果是一个 OpenSSL 格式的密文字符串。要从中获取实际密文,您需要访问其上的ciphertext 属性。

每个加密的 IV 必须是随机的,这样它在语义上是安全的。这样攻击者只看密文就无法判断多次加密的同一个明文是否真的是同一个明文。

下面是我做的一个例子。

JavaScript:

var key = CryptoJS.enc.Utf8.parse('1234567890123456'); // TODO change to something with more entropy

function encrypt(msgString, key) {
    // msgString is expected to be Utf8 encoded
    var iv = CryptoJS.lib.WordArray.random(16);
    var encrypted = CryptoJS.AES.encrypt(msgString, key, {
        iv: iv
    });
    return iv.concat(encrypted.ciphertext).toString(CryptoJS.enc.Base64);
}

function decrypt(ciphertextStr, key) {
    var ciphertext = CryptoJS.enc.Base64.parse(ciphertextStr);

    // split IV and ciphertext
    var iv = ciphertext.clone();
    iv.sigBytes = 16;
    iv.clamp();
    ciphertext.words.splice(0, 4); // delete 4 words = 16 bytes
    ciphertext.sigBytes -= 16;
    
    // decryption
    var decrypted = CryptoJS.AES.decrypt({ciphertext: ciphertext}, key, {
        iv: iv
    });
    return decrypted.toString(CryptoJS.enc.Utf8);
}

带有 pycrypto 的 Python 2 代码:

BLOCK_SIZE = 16
key = b"1234567890123456" # TODO change to something with more entropy

def pad(data):
    length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + chr(length)*length

def unpad(data):
    return data[:-ord(data[-1])]

def encrypt(message, key):
    IV = Random.new().read(BLOCK_SIZE)
    aes = AES.new(key, AES.MODE_CBC, IV)
    return base64.b64encode(IV + aes.encrypt(pad(message)))

def decrypt(encrypted, key):
    encrypted = base64.b64decode(encrypted)
    IV = encrypted[:BLOCK_SIZE]
    aes = AES.new(key, AES.MODE_CBC, IV)
    return unpad(aes.decrypt(encrypted[BLOCK_SIZE:]))

警告:请记住,python2 和 pycrypto 都已过时,因此必须调整代码以适应 python3 和 pycryptodome。


其他注意事项:

您似乎想使用密码作为密钥。密码短语通常是人类可读的,但密钥不是。您可以使用 PBKDF2、bcrypt 或 scrypt 等功能从密码短语派生密钥。

上面的代码并不完全安全,因为它缺少身份验证。未经身份验证的密文可能会导致可行的攻击和未被注意的数据操纵。通常采用 encrypt-then-MAC 方案和 HMAC-SHA256 等良好的 MAC 函数。

【讨论】:

  • 我无法在 Python 中的 AES.new 中传递 IV 和密码,因为它需要字节而不是字符串。
  • 我在这里有类似的问题stackoverflow.com/questions/65844902/… 但我在 CryptoJS CryptoJS.algo.AES.keySize = 32 CryptoJS.algo.EvpKDF.cfg.iterations = 1e4 CryptoJS.algo.EvpKDF 中有一些额外的设置.cfg.keySize = 32;而且我不知道如何用python实现它们
  • 如果你面对TypeError 上面的解决方案,例如TypeError: Object type <class 'str'> cannot be passed to C codeTypeError: ord() expected string of length 1, but int found,你可以更新你的代码如下: * 在加密函数中:return base64.b64encode(IV + aes.encrypt(pad(message).encode())).decode() * 在解密函数中:@ 987654331@ 谢谢@Artjom B. !你的解决方案拯救了我的一天。
  • @Rukamakama 是的,我的答案已经很老了,并且针对 Python 2 和 PyCrypto。两者都没有过时,因此代码不起作用,但应该很容易适应 Python 3 和 pycryptodome。
【解决方案2】:

我不得不将使用 crypto-js 库的 AES 加密/解密的 Javascript 实现移植到 Python3。

基本上,我的方法是在现有 JS 代码上运行调试器,并查看每个步骤中填充的变量。我也能够找出在 python 中做同样事情的等效方法。

这是我使用 pycryptodome 库移植它的方法,该库具有一些有用的功能。

  • AES.js
var CryptoJS = require("crypto-js");
var Base64 = require("js-base64");

function decrypt(str, secret) {
    try {
        var _strkey = Base64.decode(secret);
        var reb64 = CryptoJS.enc.Hex.parse(str);
        var text = reb64.toString(CryptoJS.enc.Base64);
        var Key = CryptoJS.enc.Base64.parse(_strkey.split(",")[1]); //secret key
        var IV = CryptoJS.enc.Base64.parse(_strkey.split(",")[0]); //16 digit
        var decryptedText = CryptoJS.AES.decrypt(text, Key, { keySize: 128 / 8, iv: IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
        return decryptedText.toString(CryptoJS.enc.Utf8); //binascii.unhexlify(decryptedText)
    } catch (e) {
        console.log("Error", e)
    }
}

function encrypt(str, secret) {
    str = Math.random().toString(36).substring(2, 10) + str;
    var _strkey = Base64.decode(secret);
    _strkey.split(",");
    var text = CryptoJS.enc.Utf8.parse(str);
    var Key = CryptoJS.enc.Base64.parse(_strkey.split(",")[1]); //secret key
    var IV = CryptoJS.enc.Base64.parse(_strkey.split(",")[0]); //16 digit
    var encryptedText = CryptoJS.AES.encrypt(text, Key, { keySize: 128 / 8, iv: IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
    var b64 = encryptedText.toString();
    var e64 = CryptoJS.enc.Base64.parse(b64);
    var eHex = e64.toLocaleString(CryptoJS.enc.Hex);
    return eHex.toUpperCase();
}

const secret = "V1VWTVRFOVhJRk5WUWsxQlVrbE9SUT09LFRrOUNUMFJaSUZkSlRFd2dTMDVQVnc9PQ=="
const data = "THIS IS MY SECRET MESSAGE!"
encData = EncryptText2(data, secret)
decData = DecryptText2(encData, secret)
console.log("encryptedData", encData)
console.log("decryptedData", decData)
  • AESify.py
import string
import random
import base64
import binascii
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

class AESify:
  def __init__(self, key=None, iv=None,secret = None, block_len=16, salt_len= 8):
    self.key = key
    self.iv = iv
    self.salt_len = salt_len
    self.block_len = block_len
    self.mode = AES.MODE_CBC
    if(secret):
      self.useSecret(secret)
    if(self.key is None and self.iv is None):
      raise Exception("No key , IV pair or secret provided")
      
  @staticmethod
  def makeSecret(key, iv):
    if(len(key) % 8 != 0):
      raise Exception("Key length must be a mutliple of 8")
    if(len(iv) % 8 != 0):
      raise Exception("Initial vector must be a multiple of 8")
    key64 = base64.b64encode(key.encode()).decode()
    iv64 = base64.b64encode(iv.encode()).decode()
    secret = iv64 + "," + key64
    secret64 = base64.b64encode(secret.encode()).decode()
    return secret64

  def useSecret(self, secret):
    iv64, key64 = base64.b64decode(secret).decode().split(",") # decode and convert to string
    self.iv = base64.b64decode(iv64)
    self.key = base64.b64decode(key64)
    return self

  def encrypt(self, text):
    text = self.add_salt(text, self.salt_len)
    cipher = AES.new(self.key, self.mode, self.iv)
    text = cipher.encrypt(pad(text.encode('utf-8'), self.block_len))
    return binascii.hexlify(text).decode()
  
  def decrypt(self, data):
    text = binascii.unhexlify(data) # UNHEX and convert the encrypted data to text
    cipher = AES.new(self.key, self.mode, self.iv)
    return unpad(cipher.decrypt(text), self.block_len).decode('utf-8')[self.salt_len:] 
  
  def add_salt(self, text, salt_len):
    # pre-pad with random salt
    salt = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(salt_len))
    text = salt + text
    return text
  • main.py
from AESify import AESify

key , iv = "NOBODY WILL KNOW", "YELLOW SUBMARINE"
# contains IV and key
secret = AESify.makeSecret(key, iv)
aes = AESify(secret= secret, block_len=16, salt_len=4)
msg = "THIS IS MY SECRET MESSAGE"
encrypted = aes.encrypt(msg)
decrypted = aes.decrypt(encrypted)
print(f"{secret=}")
print(f"{encrypted=}")
print(f"{decrypted=}")

【讨论】:

    【解决方案3】:

    注意:salt、iv、padding 在 js 和 python 中应该是一样的

    生成 salt 和 iv 值并将其转换为字节字符串 uisng CryptoJS.enc.Utf8.parse()

    js文件

    var encrypted = CryptoJS.AES.encrypt(JSON.stringify(json_data), CryptoJS.enc.Utf8.parse(data['salt']) , { iv: CryptoJS.enc.Utf8.parse(data['iv']) , mode: CryptoJS.mode.CBC , padding: CryptoJS.pad.Pkcs7});
    en_data = encrypted.ciphertext.toString(CryptoJS.enc.Base64)
    

    将此加密数据发送到 python 文件

    python 文件

    from Crypto.Util.Padding import pad, unpad
    
    ct = request.POST['encrypted_data']
    data = base64.b64decode(ct)
    cipher1 = AES.new(salt, AES.MODE_CBC, iv)
    pt = unpad(cipher2.decrypt(data), 16)
    data = json.loads(pt.decode('utf-8'))
            
    

    pycrypto 中的 pad 和 upad 默认使用 pkcs#7

    salt 和 iv 值应该是字节串

    【讨论】:

    • 名称'salt' 未定义
    猜你喜欢
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多