【问题标题】:Encrypt in JS front end and decrypt in python backend using AES GCM使用 AES GCM 在 JS 前端加密并在 python 后端解密
【发布时间】:2021-06-14 00:07:42
【问题描述】:

我正在尝试使用 AES GCM 加密算法在 JS 前端加密并在 python 后端解密。我将Web cryptography api 用于JS 前端,将python cryptography library 用于python 后端作为加密库。我现在已经在两侧固定了 IV。我在两边都实现了加密-解密代码,它们在每一边都工作。但我认为填充的方式不同,似乎无法弄清楚填充是如何在网络密码学 api 中完成的。下面是python后端的加解密:

def encrypt(derived_key, secret):
    IV = bytes("ddfbccae-b4c4-11", encoding="utf-8")
    aes = Cipher(algorithms.AES(derived_key), modes.GCM(IV))
    encryptor = aes.encryptor()
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(secret.encode()) + padder.finalize()
    return encryptor.update(padded_data) + encryptor.finalize()

def decrypt(derived_key, secret): 
    IV = bytes("ddfbccae-b4c4-11", encoding="utf-8")
    aes = Cipher(algorithms.AES(derived_key), modes.GCM(IV))
    decryptor = aes.decryptor()
    decrypted_data = decryptor.update(secret) 
    unpadder = padding.PKCS7(128).unpadder()
    return unpadder.update(decrypted_data) + unpadder.finalize()

下面是加解密代码的JS代码:

async function encrypt(secretKey, message) {
  let iv = "ddfbccae-b4c4-11";
  iv = Uint8Array.from(iv, x => x.charCodeAt(0))
  let encoded = getMessageEncoding(message);
  ciphertext = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: iv
    },
    secretKey,
    encoded
  );
  return ciphertext;
}

async function decrypt(secretKey, cipherText) {
  iv = "ddfbccae-b4c4-11";
  iv = Uint8Array.from(iv, x => x.charCodeAt(0))
  try {
    let decrypted = await window.crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv: iv
      },
      secretKey,
      cipherText
    );

    let dec = new TextDecoder();
    console.log("Decrypted message: ");
    console.log(dec.decode(decrypted));
   
  } catch (e) {
    console.log("error");
    
  }
}

我尝试在 JS 端加密并在 python 端解密。但我收到以下错误:

如果我尝试在两边加密相同的字符串,我会得到以下输出: 在python中加密文本:\x17O\xadn\x11*I\x94\x99\xc6\x90\x8a\xa9\x9cc=

在JS中密文:\x17O\xadn\x11*I\xdf\xe3F\x81(\x15\xcc\x8c^z\xdf+\x1d\x91K\xbc

如何解决这个填充问题?

【问题讨论】:

  • GCM 是一种流密码模式,不应用填充,Cryptography 也以不同的方式实现 GCM。在您的实现中,至少缺少该标签。此外,建议使用 12 字节的随机数。
  • 好吧,我的错。谢谢。

标签: javascript python cryptography webcrypto-api python-cryptography


【解决方案1】:

GCM 是一种流密码模式,因此不需要填充。加密时会隐式生成一个鉴权标签,用于解密时的鉴权。此外,建议 GCM 使用 12 字节的 IV/nonce。

与 JavaScript 代码不同,发布的 Python 代码不必要地填充并且没有考虑身份验证标签,这可能是导致密文不同的主要原因。这是否是唯一的原因以及JavaScript代码是否正确实现了GCM,很难说,因为getMessageEncoding()方法没有发布,所以无法测试。

此外,两个代码都应用 16 字节 IV/nonce,而不是推荐的 12 字节 IV/nonce。


密码学 为 GCM 提供了两种可能的实现。一种实现使用非身份验证模式的体系结构,如 CBC。发布的 Python 代码应用了这种设计,但没有考虑身份验证,因此没有完全实现 GCM。可以在 here 找到此设计的正确示例。
密码学 通常推荐 GCM 的另一种方法(如 Danger 注释),即 AESGCM 类,它执行隐式身份验证,这样就不会被意外忘记或错误地实现。

以下实现使用AESGCM 类(并且还考虑了可选的附加身份验证数据):

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
#import os

#key = AESGCM.generate_key(bit_length=256)    
#nonce = os.urandom(12)
key = base64.b64decode('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE=') # fix for testing, AES-256
nonce = base64.b64decode('MDEyMzQ1Njc4OTAx') # fix for testing, 12 bytes

plaintext = b'The quick brown fox jumps over the lazy dog'
aad = b'the aad' # aad = None without additional authenticated data

aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
print('Ciphertext (B64): ' + base64.b64encode(ciphertext).decode('utf8'))
decrypted = aesgcm.decrypt(nonce, ciphertext, aad)
print('Decrypted:        ' + decrypted.decode('utf8'))

输出:

Output
Ciphertext (B64): JOetStCANhPISvQ6G6IcRBauqbtC8fzRooblayHqkqSPKzLbidx/gBWfLNzBC+ZpcAGnGnHXaI7CB1U=
Decrypted:        The quick brown fox jumps over the lazy dog

身份验证标签附加到密文中,因此(Base64解码)结果为明文长度(43字节)加上标签长度(16字节,默认),一共59字节。

对于测试,使用预定义的密钥和 IV/nonce 来与 JavaScript 代码的结果进行比较。请注意,在实践中,出于安全原因,密钥/IV 对只能使用一次,这对于 GCM 模式尤其重要,例如here。因此,通常会为每次加密生成随机 IV/nonce。


WebCrypto API 是用于加密的低级 API,不提供 Base64 编码/解码方法。在下文中,为简单起见,使用js-base64。就像 Python 代码一样,标签被附加到密文中。

使用 Python 代码的密钥和 IV/nonce 的 AES-GCM 可能实现在功能上与发布的 JavaScript 代码基本相同:

(async () => {      
    var key = Base64.toUint8Array('MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDE='); // fix for testing, AES-256
    var nonce = Base64.toUint8Array('MDEyMzQ1Njc4OTAx'); // fix for testing, 12 bytes

    var plaintext = new TextEncoder().encode("The quick brown fox jumps over the lazy dog");
    var aad = new TextEncoder().encode('the aad');
                
    var keyImported = await await crypto.subtle.importKey(
        "raw",
        key,
        { name: "AES-GCM" },
        true,
        ["decrypt", "encrypt"]
    );
                
    var ciphertext = await await crypto.subtle.encrypt(
        { name: "AES-GCM", iv: nonce, additionalData: aad }, // { name: "AES-GCM", iv: nonce } without additional authenticated data
        keyImported,
        plaintext
    );
    console.log('Ciphertext (Base64):\n', Base64.fromUint8Array(new Uint8Array(ciphertext)).replace(/(.{48})/g,'$1\n'));
              
    var decrypted = await await crypto.subtle.decrypt(
        { name: "AES-GCM", iv: nonce, additionalData: aad }, // { name: "AES-GCM", iv: nonce } without additional authenticated data
        keyImported,
        ciphertext
    );
    console.log('Plaintext:\n', new TextDecoder().decode(decrypted).replace(/(.{48})/g,'$1\n'));
})();
<script src="https://cdn.jsdelivr.net/npm/js-base64@3.2.4/base64.min.js"></script>

输出:

Ciphertext (Base64):
 JOetStCANhPISvQ6G6IcRBauqbtC8fzRooblayHqkqSPKzLbidx/gBWfLNzBC+ZpcAGnGnHXaI7CB1U=
Plaintext:
 The quick brown fox jumps over the lazy dog

其中密文与 Python 代码相同。

【讨论】:

  • 太棒了!再次感谢@Topaco
猜你喜欢
  • 1970-01-01
  • 2021-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-18
  • 2021-09-30
  • 2015-02-18
相关资源
最近更新 更多