您现在的位置是:首页 >技术交流 >C# AES/GCM 加密网站首页技术交流

C# AES/GCM 加密

白菜先森 2024-06-19 18:01:02
简介C# AES/GCM 加密

在开发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位的随机值,然后和加密文本一起返给解密端即可。

以下是几种不同库的加解密代码

1、System.Security.Cryptography

备注:.net版本大于5.0才支持GCM加密

这种加密方式是需要自己把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);
        }
    }
    }

2、Org.BouncyCastle.Crypto (已弃用,但可以用☺)

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);
        }
    }
}

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。