• 用Portable.BouncyCastle来进行加解密的代码demo


    前言

    这里对之前对接的公司中的代码demo做一个总结,原本为清一色的java,哈哈。这里都转成C#。用到的库是Portable.BouncyCastle官网。之前也是准备用.net core 内置的类,方法,但实际在用的时候比如因为desKey并不是特定长度的,导致抛了一些异常,于是就改用了这个库。

    这个库中有如下这么一段的介绍:

    The lightweight API works with everything from the J2ME to the JDK 1.7 and there is also an API in C# providing equivalent functionality for most of the above.

    实际用下来也确实如此,方法基本上是“相同”名字的。

    正文

    Des

    这是一种对称密钥加密算法,也就是说加密解密的秘钥是一样的。

    1. 加密
            public static string DesEncrypt(string dataXml, string desKey)
            {
                var keyParam = ParameterUtilities.CreateKeyParameter("DES", Convert.FromBase64String(desKey));
                var cipher = (BufferedBlockCipher) CipherUtilities.GetCipher("DES/NONE/PKCS5Padding");
    
                cipher.Init(true, keyParam);
                var bs = Encoding.UTF8.GetBytes(dataXml);
                var rst = cipher.DoFinal(bs);
                // var asciiBs = Encoding.ASCII.GetBytes(Encoding.UTF8.GetString(rst));
                return Convert.ToBase64String(rst);
            }
    

    看方法签名,需要两个参数,1.要加密的内容,2.加密的秘钥。先用desKey作为参数创建一个keyParam。然后用DES/NONE/PKCS5Padding作为参数获取一个cipher,这个参数用来详细描述des加密时候的相关细节,具体是根据对方给的代码里面来的。然后对这个cipher做一个初始化,第一个参数true表示这个cipher是用来加密的,并且传入之前的keyParam。然后获取加密内容的字节数组,编码是utf-8,一般都是这个编码。然后调用cipher的DoFinal方法就能获取加密之后的内容了。最后一行转成了一个base64字符串。

    通过这个方法就能对数据进行des加密了。中间也涉及了字符编码格式,base64转换的内容,这些是根据给的demo写的。

    1. 解密
            public static string DesDecrypt(string text, string desKey)
            {
                var keyParam = ParameterUtilities.CreateKeyParameter("DES", Convert.FromBase64String(desKey));
                var cipher = (BufferedBlockCipher) CipherUtilities.GetCipher("DES/NONE/PKCS5Padding");
    
                cipher.Init(false, keyParam);
                var bs = Convert.FromBase64String(text);
                var rst = cipher.DoFinal(bs);
                return Encoding.UTF8.GetString(rst);
            }
    

    下面是解密方法。两个参数,1.需要解密的内容。 2.解密的key。这个key是和加密的时候一样的。首先也是通过desKey获取一个keyParam,然后用DES/NONE/PKCS5Padding参数获取一个cipher。然后用false参数初始化这个cipher为解密用的。获取base64编码过的字节数组,调用DoFinal方法解密字节数组。解密出来的字节数组再用utf-8编码获取实际的字符串,这个是和前面的加密方法对应的。

    MD5

    这个用的是core框架自带的方法。

    1. 加密

    因为要区别BouncyCastle中的MD5类,所以对引用取一下别名。

    using SystemX = System.Security.Cryptography;
    
            public static string Md5(string data)
            {
                var text = data;
                using (var md5 = SystemX.MD5.Create())
                {
                    var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(text));
                    return BitConverter.ToString(bs).Replace("-", "").ToLower();
                }
            }
    

    方法就一个参数,需要做md5的数据。首先是创建一个md5的实例。然后取加密内容的字节数组,再调用ComputeHash方法对数组做hash值计算,然后转成16进制的字符串,去掉-字符,最后转小写。

    1. 没有解密,你懂的。

    SHA-1

    SHA-1可以生成一个被称为消息摘要的160(20字节)散列值,散列值通常的呈现形式为40个十六进制数。copy自维基百科。

            public static string ComputeSha1(string text)
            {
                Sha1Digest sha1Digest = new Sha1Digest();
                var retValue = new byte[sha1Digest.GetDigestSize()];
                var bs = Encoding.UTF8.GetBytes(text);
                sha1Digest.BlockUpdate(bs, 0, bs.Length);
                sha1Digest.DoFinal(retValue, 0);
                return BitConverter.ToString(retValue).Replace("-", "");
    
            }
    

    这个方法和SHA-256很像,在SHA-256解释。

    SHA-256

            public static string ComputeSha256(string text)
            {
                Sha256Digest sha256Digest = new Sha256Digest();
                var retValue = new byte[sha256Digest.GetDigestSize()];
                var bs = Encoding.UTF8.GetBytes(text);
                sha256Digest.BlockUpdate(bs, 0, bs.Length);
                sha256Digest.DoFinal(retValue, 0);
                return BitConverter.ToString(retValue).Replace("-", "");
            }
    

    下面是当时的java-demo

    	SHA256Digest digester = new SHA256Digest();
        byte[] retValue = new byte[digester.getDigestSize()];
        digester.update(key.getBytes(), 0, key.length());
        digester.doFinal(retValue, 0);
        return retValue;
    

    对比一下两份代码,基本是一样的。首先是实例化一个Sha256Digest,然后获取原文的字节数组,然后用这个Sha256Digest去更新内容,最后输出到retValue数组中。

    SHA家族很庞大,224,256,384,512等等等。用法都一样。

    SignEnvelop

    这个方法有点复杂,取这个名字是因为demo就是这么写的。

            private static byte[] EncryptEnvelop(X509Certificate certificate, byte[] bsOrgData)
            {
                var gen = new CmsEnvelopedDataGenerator();
                var data = new CmsProcessableByteArray(bsOrgData);
                gen.AddKeyTransRecipient(certificate);
    
                var enveloped = gen.Generate(data, CmsEnvelopedDataGenerator.DesEde3Cbc);
                var a = enveloped.ContentInfo.ToAsn1Object();
                return a.GetEncoded();
            }
    
            /// <summary>
            /// pfx文件密码
            /// </summary>
            private const string pfxPwd = "sss";
            /// <summary>
            /// pfx证书,主要是拿私钥
            /// </summary>
            public static string PfxPath => Path.Combine(AppContext.BaseDirectory, "rsa", "sss.pfx");
            /// <summary>
            /// cer证书,拿公钥
            /// </summary>
            public static string CertPath => Path.Combine(AppContext.BaseDirectory, "rsa", "sss.cer");
    
            public static string SignEnvelop(string orgData)
            {
                Pkcs12StoreBuilder pkcs12StoreBuilder = new Pkcs12StoreBuilder();
                var pkcs12Store = pkcs12StoreBuilder.Build();
                pkcs12Store.Load(File.OpenRead(PfxPath), pfxPwd.ToCharArray());
                IEnumerable aliases = pkcs12Store.Aliases;
                var enumerator = aliases.GetEnumerator();
                enumerator.MoveNext();
                var alias = enumerator.Current.ToString();
                //从pfx文件中获取CmsSignedData需要的key。
                var privKey = pkcs12Store.GetKey(alias);
    
                var x509Cert = pkcs12Store.GetCertificate(alias).Certificate;
    
                var bs = Encoding.UTF8.GetBytes(orgData);
    
                CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
                gen.AddSignerInfoGenerator(
                    new SignerInfoGeneratorBuilder().Build(new Asn1SignatureFactory("SHA1withRSA", privKey.Key), x509Cert));
                IList certList = new ArrayList();
                certList.Add(x509Cert);
                gen.AddCertificates(X509StoreFactory.Create("Certificate/Collection",
                    new X509CollectionStoreParameters(certList)));
                var msg = new CmsProcessableByteArray(bs);
                var sigData = gen.Generate(msg, true);
                var signData = sigData.GetEncoded();
    
                var certificate = DotNetUtilities.FromX509Certificate(new SystemX509.X509Certificate(CertPath));
                var rst = Convert.ToBase64String(EncryptEnvelop(certificate, signData));
                return rst;
            }
    

    这里有两个方法,拆分一下,主要有以下几个demo功能。

    • 使用Pkcs12StoreBuilder从pfx文件中获取CmsSignedData需要的key。
    • 使用DotNetUtilities 从cer文件中获取X509Certificate对象。

    RSA

    读取pem文件中的公钥做加密,这里用到了一个分段加密的逻辑。

    文件参数

            private static string publicKeyFile = Path.Combine(AppContext.BaseDirectory, "rsa", "rsa_public_key.pem");
            private static string privateKeyFile = Path.Combine(AppContext.BaseDirectory, "rsa", "rsa_private_key.pem");
    
    1. 用公钥对数据进行分段加密
            public static string GetNonce(string randomStr)
            {
    
                var maxBlock = 245;
                int offset = 0;
                int i = 0;
                var outBytes = new List<byte>();
    
                var pubKey = new PemReader(new StreamReader(publicKeyFile)).ReadObject() as AsymmetricKeyParameter;
                IBufferedCipher c = CipherUtilities.GetCipher("RSA/NONE/PKCS1PADDING");// 参数与JAVA中解密的参数一致
                c.Init(true, pubKey);
                var data = Encoding.UTF8.GetBytes(randomStr);
                var inputLength = data.Length;
                while (inputLength - offset > 0)
                {
                    if (inputLength - offset > maxBlock)
                    {
                        outBytes.AddRange(c.DoFinal(data, offset, maxBlock));
                    }
                    else
                    {
                        outBytes.AddRange(c.DoFinal(data, offset, inputLength - offset));
                    }
                    i++;
                    offset = i * maxBlock;
                }
                return Convert.ToBase64String(outBytes.ToArray());
            }
    

    主要是用PemReader对象对pem文件进行读写操作。因为是RSA加密的,所以对象转换成AsymmetricKeyParameter。其他的就和之前的DES之类的类似。

    1. 用私钥以及MD5withRSA对数据算签名
            public static string GetSignature(string text)
            {
                var bsToEncrypt = Encoding.UTF8.GetBytes(text);
                PemReader pemReader = new PemReader(new StreamReader(privateKeyFile));
                var pem = (AsymmetricCipherKeyPair)pemReader.ReadObject();
                ISigner sig = SignerUtilities.GetSigner("MD5withRSA");
                sig.Init(true, pem.Private);
                sig.BlockUpdate(bsToEncrypt, 0, bsToEncrypt.Length);
                byte[] signature = sig.GenerateSignature();
    
                /* Base 64 encode the sig so its 8-bit clean */
                var signedString = Convert.ToBase64String(signature);
    
                return signedString;
            }
    

    主要还是PemReader对象的使用以及使用ISigner构造一个签名工具。

    1. 解密
            private static byte[] Decrypt(byte[] input, string privateKeyPath)
            {
                PemReader r = new PemReader(new StreamReader(privateKeyPath));     //载入私钥
                var readObject = r.ReadObject();
    
                AsymmetricCipherKeyPair priKey = (AsymmetricCipherKeyPair)readObject;
                string mode = "RSA/NONE/PKCS1Padding";
                IBufferedCipher c = CipherUtilities.GetCipher(mode);
                c.Init(false, priKey.Private);
    
                byte[] outBytes = c.DoFinal(input);
                return outBytes;
            }
    
            public static string RSADecryptByPrivateKey(string text)
            {
                if (string.IsNullOrEmpty(text))
                {
                    return string.Empty;
                }
                var bs = Convert.FromBase64String(text);
                var rst = new List<byte>();
                #region 分段解密 解决加密密文过长问题
                int len = 256;
                int m = bs.Length / len;
                if (m * len != bs.Length)
                {
                    m = m + 1;
                }
    
                for (int i = 0; i < m; i++)
                {
                    byte[] temp = new byte[256];
    
                    if (i < m - 1)
                    {
                        temp = bs.Skip(i * len).Take(len).ToArray();
                    }
                    else
                    {
                        temp = new byte[bs.Length % len == 0 ? 1 * len : bs.Length % len];
                        bs.Skip(i * len).Take(bs.Length % len == 0 ? len : bs.Length % len).ToArray().CopyTo(temp, 0);
                    }
                    rst.AddRange(Decrypt(temp, privateKeyFile));
                }
                #endregion
    
                return Encoding.UTF8.GetString(rst.ToArray());
    
            }
    

    因为加密是分段的,所以解密也需要分段,套路和之前一样。

    完。

  • 相关阅读:
    zoj 3792 Romantic Value
    uva 563
    uva 10779 Collectors Problem 网络流
    什么是撞库,如何预防撞库攻击?
    linux install redis-cli
    python远程调试及celery调试
    python HttpServer共享文件
    python引用,浅复制,深复制
    redis 查询key数量
    ubuntu查询可用安装包
  • 原文地址:https://www.cnblogs.com/sheldon-lou/p/10414363.html
Copyright © 2020-2023  润新知