C# 中的 AEAD_AES_256_GCM


C# 中的 AEAD_AES_256_GCM

  • 注意:AEAD_AES_256_GCM Key的長度必須是32位,nonce的長度必須是12位,附加數據有可能為空值。AEAD_AES_128_GCM Key的長度必須是16位,nonce的長度必須是12位,附加數據有可能為空值。

使用中AEAD_AES_256_GCM還是AEAD_AES_128_GCM加密,是根據key的長度來決定的。
size = key.Length * 8
256 = 32 * 8, AEAD_AES_256_GCM的key長度必須是 32 位。
128 = 16 * 8, AEAD_AES_128_GCM的key長度必須是 16 位。

英文好的可以看這個文檔rfc5116

使用BouncyCastle類庫實現

安裝 Install-Package Portable.BouncyCastle -Version 1.8.9。 微信支付官方代碼中同樣使用了 BouncyCastle

/// <summary>
/// 使用BouncyCastle進行AEAD_AES_256_GCM 解密
/// </summary>
/// <param name="key">key:32位字符</param>
/// <param name="nonce">隨機串12位</param>
/// <param name="cipherData">密文(Base64字符)</param>
/// <param name="associatedData">附加數據可能null</param>
/// <returns></returns>
static string AesGcmDecryptByBouncyCastle(string key, string nonce, string cipherData, string associatedData)
{
    var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

    var gcmBlockCipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(
        new KeyParameter(Encoding.UTF8.GetBytes(key)),
        128,  //128 = 16 * 8 => (tag size * 8)
        Encoding.UTF8.GetBytes(nonce),
        associatedBytes);
    gcmBlockCipher.Init(false, parameters);

    var data = Convert.FromBase64String(cipherData);
    var plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
    
    var length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
    gcmBlockCipher.DoFinal(plaintext, length);
    return Encoding.UTF8.GetString(plaintext);
}

/// <summary>
/// 使用BouncyCastle進行AEAD_AES_256_GCM 加密
/// </summary>
/// <param name="key">key32位字符</param>
/// <param name="nonce">隨機串12位</param>
/// <param name="plainData">明文</param>
/// <param name="associatedData">附加數據可能null</param>
/// <returns></returns>
static string AesGcmEncryptByBouncyCastle(string key, string nonce, string plainData, string associatedData)
{
    var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

    var gcmBlockCipher = new GcmBlockCipher(new AesEngine());
    var parameters = new AeadParameters(
        new KeyParameter(Encoding.UTF8.GetBytes(key)),
        128, //128 = 16 * 8 => (tag size * 8)
        Encoding.UTF8.GetBytes(nonce),
        associatedBytes);
    gcmBlockCipher.Init(true, parameters);

    var data = Encoding.UTF8.GetBytes(plainData);
    var cipherData = new byte[gcmBlockCipher.GetOutputSize(data.Length)];

    var length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, cipherData, 0);
    gcmBlockCipher.DoFinal(cipherData, length);
    return Convert.ToBase64String(cipherData);
}

使用官方AesGcm類

自aspnetcore3.0之后,System.Security.Cryptography支持 AES_GCM,不支持.net framework。在加密時,請使用AesGcmEncryptToBase64_WithTag方法,這個方法的結果包含了tag (authentication tag)。加密結果最后面的16位字符就是tag,但是官方的加密方法沒有幫我們拼接上。

/// <summary>
/// 使用 AesGcm 解密
/// </summary>
/// <param name="key">key32位字符</param>
/// <param name="nonce">隨機串12位</param>
/// <param name="encryptedData">密文(Base64字符)</param>
/// <param name="associatedData">(可能null)</param>
/// <returns></returns>
static string AesGcmDecryptFromBase64(string key, string nonce, string encryptedData, string associatedData)
{
    var keyBytes = Encoding.UTF8.GetBytes(key);
    var nonceBytes = Encoding.UTF8.GetBytes(nonce);
    var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);
    
    var encryptedBytes = Convert.FromBase64String(encryptedData);
    //tag size is 16
    var cipherBytes = encryptedBytes[..^16];
    var tag = encryptedBytes[^16..];
    var decryptedData = new byte[cipherBytes.Length];
    using var cipher = new AesGcm(keyBytes);
    cipher.Decrypt(nonceBytes, cipherBytes, tag, decryptedData, associatedBytes);
    return Encoding.UTF8.GetString(decryptedData);
}

/// <summary>
/// 使用 AesGcm AEAD_AES_256_GCM 加密,不要在正式環境中使用這個方法。因為在解密時不知道tag,除非額外返回tag。
/// </summary>
/// <param name="key">key32位字符</param>
/// <param name="nonce">隨機串12位</param>
/// <param name="plainData">明文</param>
/// <param name="associatedData">附加數據(可能null)</param>
/// <returns>只返回加密數據不包含authentication tag</returns>
static string AesGcmEncryptToBase64(string key, string nonce, string plainData, string associatedData)
{
    var keyBytes = Encoding.UTF8.GetBytes(key);
    var nonceBytes = Encoding.UTF8.GetBytes(nonce);
    var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

    var plainBytes = Encoding.UTF8.GetBytes(plainData);
    var cipherBytes = new byte[plainBytes.Length];
    using var cipher = new AesGcm(keyBytes);
    //tag size must be is 16 
    var tag = new byte[16];
    cipher.Encrypt(nonceBytes, plainBytes, cipherBytes, tag, associatedBytes);
    
    return Convert.ToBase64String(cipherBytes);
}

/// <summary>
/// 使用 AesGcm進行AEAD_AES_256_GCM加密
/// </summary>
/// <param name="key">key32位字符</param>
/// <param name="nonce">隨機串12位</param>
/// <param name="plainData">明文</param>
/// <param name="associatedData">附加數據(可能null)</param>
/// <returns>base64(加密后數據 + authentication tag)</returns>
static string AesGcmEncryptToBase64_WithTag(string key, string nonce, string plainData, string associatedData)
{
    var keyBytes = Encoding.UTF8.GetBytes(key);
    var nonceBytes = Encoding.UTF8.GetBytes(nonce);
    var associatedBytes = associatedData == null ? null : Encoding.UTF8.GetBytes(associatedData);

    var plainBytes = Encoding.UTF8.GetBytes(plainData);
    var cipherBytes = new byte[plainBytes.Length];
    //tag size is 16
    var tag = new byte[16];
    using var cipher = new AesGcm(keyBytes);
    cipher.Encrypt(nonceBytes, plainBytes, cipherBytes, tag, associatedBytes);
    
    var cipherWithTag = new byte[cipherBytes.Length + tag.Length];
    Buffer.BlockCopy(cipherBytes, 0, cipherWithTag, 0, cipherBytes.Length);
    Buffer.BlockCopy(tag, 0, cipherWithTag, cipherBytes.Length, tag.Length);
    
    return Convert.ToBase64String(cipherWithTag);
}

在python中的實現

我們可以使用python代碼來檢驗加密和解密結果。

pip install cryptography
pip install paramiko
# python 3.9

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

def decrypt(key, nonce, ciphertext, associated_data):
    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)
    aesgcm = AESGCM(key_bytes)
    return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
    
def encrypt(key, nonce, plaintext, associated_data):
    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = str.encode(plaintext)
    aesgcm = AESGCM(key_bytes)
    return aesgcm.encrypt(nonce_bytes, data, ad_bytes)
    


def main():
    key = '1234567890_1234567890_1234567890'
    nonce = '77d0a5ff3937'
    associated = '6df132d42d0b4581'
    text = "{'A':123, count:'10', isOk:false, body:{'Text':'select * from table_A where name=@_p1', result:[{'id':1, 'age':23}]}}"
    
    crypt_bytes = encrypt(key, nonce, text, associated)           
    print("密文:%d位", len(crypt_bytes))
    cryptText = base64.b64encode(crypt_bytes)
    print(cryptText)

    plain_bytes = decrypt(key, nonce, cryptText, associated)
    print("明文:%d位", len(plain_bytes))
    print(bytes.decode(plain_bytes))


if __name__ == '__main__':
    main()

測試代碼

var text = "{'A':123, count:'10', isOk:false, body:{'Text':'select * from table_A where name=@_p1', result:[{'id':1, 'age':23}]}}";
//KEY 必須是兩個32位
var key = "1234567890_1234567890_1234567890";

var nonce = "77d0a5ff3937";//Guid.NewGuid().ToString("N").Substring(0, 12);


var associated = "6df132d42d0b4581"; //Guid.NewGuid().ToString("N").Substring(0, 16);

var pythonResult = "PrTO/594j0CYMi2CQF9IFIp7UNkiTtIiIUbmR+jv1c1iO8Ng/HDFHDjL2t0DYo7xo5Vr0O0fUg9hD3bfCoomP+taVaPrW2kJbPTiFXkohXk3T80lQIdWP5lrl21vJvZO3MbmvshyjU+Oxk7pSnjiE5mw/sPXBs4jzS5wtvLUgHvWGaNxzw==";

Console.WriteLine();

var cipherText = AesGcmEncryptToBase64(key, nonce, text, nonce);

Console.WriteLine($"原始密文Base64  :\t{cipherText}");

//Console.WriteLine($"密文tag Base64:\t\t{AesGcmEncryptToBase64(keyBytes,  nonceBytes,  text, associatedBytes)}");

Console.WriteLine($"Python GCM密文  :\t{pythonResult}");

var cryptText1 = AesGcmEncryptByBouncyCastle(key, nonce, text, associated);
Console.WriteLine($"BouncyCastle密文: \t{cryptText1} ");

var cryptText2 = AesGcmEncryptToBase64_WithTag(key, nonce,  text, associated);
Console.WriteLine($"密文+Tag Base64 : \t{cryptText2} ");

Console.WriteLine();
Console.WriteLine();

var t30 = AesGcmDecryptByBouncyCastle(key, nonce, pythonResult, associated);
Console.WriteLine($"BouncyCastle 解密 Python      :{t30} \tisOk:{text == t30}");

var t40 = AesGcmDecryptFromBase64(key, nonce, pythonResult, associated);
Console.WriteLine($"AesGcm       解密 Python      :{t40} \tisOk:{text == t40}");

Console.WriteLine();
Console.WriteLine();

var t31 = AesGcmDecryptByBouncyCastle(key, nonce, cryptText1, associated);
Console.WriteLine($"BouncyCastle 解密 BouncyCastle:{t31} \tisOk:{text == t31}");

var t41 = AesGcmDecryptFromBase64(key, nonce, cryptText1, associated);
Console.WriteLine($"AesGcm       解密 BouncyCastle:{t41} \tisOk:{text == t41}");

Console.WriteLine();
Console.WriteLine();

var t32 = AesGcmDecryptByBouncyCastle(key, nonce, cryptText2, associated);
Console.WriteLine($"BouncyCastle 解密 密文+Tag    :{t32} \tisOk:{text == t32}");

var t42 = AesGcmDecryptFromBase64(key, nonce, cryptText2, associated);
Console.WriteLine($"AesGcm       解密 密文+Tag    :{t42} \tisOk:{text == t42}");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM