在开发fiddler 插件时遇到GCM加密的一些问题。
先了解下GCM加密中的几个概念:
1、初始向量IV:参数向量IV的值相当于GCM使用的Nonce;
2、附加消息:GCM专用,由加解密双端共享,AAD可以为空;
3、消息认证码:GCM加密后生成的消息认证码TAG,GCM解密时专用。
4、填充模式:AES支持的填充模式为PKCS7和NONE不填充。
5、密钥KEY:AES标准规定区块长度只有一个值,固定为128Bit,对应的字节为16位。AES算法规定密钥长度只有三个值,128Bit、192Bit、256Bit,对应的字节为16位、24位和32位,其中密钥KEY不能公开传输,用于加密解密数据;
注①:初始化向量IV:该字段可以公开,用于将加密随机化。同样的明文被多次加密也会产生不同的密文,避免了较慢的重新产生密钥的过程,初始化向量与密钥相比有不同的安全性需求,因此IV通常无须保密。然而在大多数情况中,不应当在使用同一密钥的情况下两次使用同一个IV,一般推荐初始化向量IV为12位的随机值。在实际的使用场景中,它不能像密钥KEY那样直接保存在配置文件或固定写死在代码中,一般正确的处理方式为:在加密端将IV设置为一个12位的随机值,然后和加密文本一起返给解密端即可。
以下是几种不同库的加解密代码
这种加密方式是需要自己把tag和密文拼接在一起,java的crypto加密库直接合并了这一步骤,因此跨平台加解密时需要注意下
using System.Security.Cryptography;
using System.Text;
namespace test
{
class Program
{
static void Main(string[] args)
{
string EncryptStr = Encrypt("hello world", "F0z32lQdzx+XcanP");
Console.WriteLine(EncryptStr);
string decryptStr = Decrypt(EncryptStr, "F0z32lQdzx+XcanP");
Console.WriteLine(decryptStr);
}
public static string Encrypt(string data, string aesKey, byte[] associatedData = null)
{
byte[] key = Encoding.UTF8.GetBytes(aesKey);
// 设置AesGcm实例
using (var aesGcm = new AesGcm(key))
{
// 生成12字节的随机数
byte[] nonce = new byte[AesGcm.NonceByteSizes.MaxSize];
RandomNumberGenerator.Fill(nonce);
// 准备输入和输出数据缓冲区
byte[] plaintext = Encoding.UTF8.GetBytes(data);
byte[] ciphertext = new byte[plaintext.Length];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
// 对输入数据进行加密
aesGcm.Encrypt(nonce, plaintext, ciphertext, tag, associatedData);
// 将随机数、密文、和tag合并为一个字节数组
byte[] result = new byte[AesGcm.NonceByteSizes.MaxSize + ciphertext.Length + AesGcm.TagByteSizes.MaxSize];
nonce.CopyTo(result, 0);
ciphertext.CopyTo(result, AesGcm.NonceByteSizes.MaxSize);
tag.CopyTo(result, AesGcm.NonceByteSizes.MaxSize + ciphertext.Length);
return Convert.ToBase64String(result);
}
}
//解密
public static string Decrypt(string data, string aesKey, byte[] associatedData = null)
{
byte[] key = Encoding.UTF8.GetBytes(aesKey);
// 使用Base64解码将字符串转换为字节数组
byte[] encryptedData = Convert.FromBase64String(data);
// 提取密文、随机数和标签
byte[] nonce = new byte[AesGcm.NonceByteSizes.MaxSize];
byte[] ciphertext = new byte[encryptedData.Length - AesGcm.NonceByteSizes.MaxSize - AesGcm.TagByteSizes.MaxSize];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
Array.Copy(encryptedData, 0, nonce, 0, nonce.Length);
Array.Copy(encryptedData, nonce.Length, ciphertext, 0, ciphertext.Length);
Array.Copy(encryptedData, nonce.Length + ciphertext.Length, tag, 0, tag.Length);
byte[] decryptedData = new byte[ciphertext.Length];
// 使用关联数据和标签解密数据
using (var aesGcm = new AesGcm(key))
{
aesGcm.Decrypt(nonce, ciphertext, tag, decryptedData, associatedData);
}
return Encoding.UTF8.GetString(decryptedData);
}
}
}
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Security.Cryptography;
using System.Text;
namespace AesGcmExample
{
class Program
{
static void Main(string[] args)
{
String plaintext = "hello world";
String key = "F0z32lQdzx+XcanP";
String ciphertext = AesGcmEncrypt(plaintext, key);
// Decrypt
String decrypted = AesGcmDecrypt(ciphertext, key);
Console.WriteLine(decrypted);
}
static String AesGcmEncrypt(string content, string aesKey, byte[] associatedData = null)
{
byte[] data = Encoding.UTF8.GetBytes(content);
byte[] key = Encoding.UTF8.GetBytes(aesKey);
//生成一个12字节的随机字节数组
byte[] nonce = new byte[12];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(nonce);
}
GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, nonce, associatedData);
cipher.Init(true, parameters);
byte[] ciphertext = new byte[cipher.GetOutputSize(content.Length)];
int len = cipher.ProcessBytes(data, 0, data.Length, ciphertext, 0);
//这个库加密完成会直接把tag放在密文最后面,因此,加解密不需要关注tag,跟其他库不太一致,需要注意下
cipher.DoFinal(ciphertext, len);
byte[] result = new byte[nonce.Length + ciphertext.Length];
nonce.CopyTo(result, 0);
ciphertext.CopyTo(result, nonce.Length);
//返回加密字符串
return Convert.ToBase64String(result);
}
static String AesGcmDecrypt(string data, string aesKey, byte[] associatedData = null)
{
byte[] encryptedData = Convert.FromBase64String(data);
byte[] key = Encoding.UTF8.GetBytes(aesKey);
byte[] nonce = new byte[12];
byte[] ciphertextbyte = new byte[encryptedData.Length - 12];
//加密向量放在了最前面12位数组
Array.Copy(encryptedData, 0, nonce, 0, nonce.Length);
Array.Copy(encryptedData, nonce.Length, ciphertextbyte, 0, ciphertextbyte.Length);
GcmBlockCipher cipher = new GcmBlockCipher(new AesFastEngine());
AeadParameters parameters = new AeadParameters(new KeyParameter(key), 128, nonce, associatedData);
cipher.Init(false, parameters);
byte[] decryptedData = new byte[cipher.GetOutputSize(ciphertextbyte.Length)];
int len = cipher.ProcessBytes(ciphertextbyte, 0, ciphertextbyte.Length, decryptedData, 0);
cipher.DoFinal(decryptedData, len);
//返回解密字符串
return Encoding.UTF8.GetString(decryptedData);
}
}
}