前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用Portable.BouncyCastle来进行加解密的代码demo

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

作者头像
_淡定_
发布2019-03-06 17:39:48
1.2K0
发布2019-03-06 17:39:48
举报
文章被收录于专栏:dotnet & javadotnet & java

前言

这里对之前对接的公司中的代码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. 加密
代码语言:javascript
复制
        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. 解密
代码语言:javascript
复制
        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类,所以对引用取一下别名。

代码语言:javascript
复制
using SystemX = System.Security.Cryptography;
代码语言:javascript
复制
        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自维基百科。

代码语言:javascript
复制
        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

代码语言:javascript
复制
        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

代码语言:javascript
复制
    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就是这么写的。

代码语言:javascript
复制
        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();
        }
代码语言:javascript
复制
        /// <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");
代码语言:javascript
复制
        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文件中的公钥做加密,这里用到了一个分段加密的逻辑。

文件参数

代码语言:javascript
复制
        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. 用公钥对数据进行分段加密
代码语言:javascript
复制
        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对数据算签名
代码语言:javascript
复制
        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. 解密
代码语言:javascript
复制
        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;
        }
代码语言:javascript
复制
        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());

        }

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

完。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-02-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • Des
      • MD5
        • SHA-1
          • SHA-256
            • SignEnvelop
              • RSA
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档