美文网首页
非对称加密算法 (RSA、DSA、ECC、DH)

非对称加密算法 (RSA、DSA、ECC、DH)

作者: 小波同学 | 来源:发表于2022-04-28 19:45 被阅读0次

    一、简介

    1.1 概念

    非对称加密需要两个密钥:公钥(publickey) 和私钥 (privatekey)。公钥和私钥是一对,如果用公钥对数据加密,那么只能用对应的私钥解密。如果用私钥对数据加密,只能用对应的公钥进行解密。因为加密和解密用的是不同的密钥,所以称为非对称加密。

    非对称加密算法的保密性好,它消除了最终用户交换密钥的需要。但是加解密速度要远远慢于对称加密,在某些极端情况下,甚至能比对称加密慢上1000倍。

    1.2 特点

    算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。对称密码体制中只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。所以保证其安全性就是保证密钥的安全,而非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。这样安全性就大了很多。

    1.3 工作原理

    • (1) A 要向 B 发送信息,A 和 B 都要产生一对用于加密和解密的公钥和私钥。
    • (2) A 的私钥保密,A 的公钥告诉 B;B 的私钥保密,B 的公钥告诉 A。
    • (3) A 要给 B 发送信息时,A 用 B 的公钥加密信息,因为 A 知道 B 的公钥。
    • (4) A 将这个消息发给 B (已经用 B 的公钥加密消息)。
    • (5) B 收到这个消息后,B 用自己的私钥解密 A 的消息。其他所有收到这个报文的人都无法解密,因为只有 B 才有 B 的私钥。

    1.4 主要算法

    RSA、Elgamal、背包算法、Rabin、D-H、ECC (椭圆曲线加密算法)。使用最广泛的是 RSA 算法,Elgamal 是另一种常用的非对称加密算法。

    1.5 应用场景

    • (1) 信息加密

    收信者是唯一能够解开加密信息的人,因此收信者手里的必须是私钥。发信者手里的是公钥,其它人知道公钥没有关系,因为其它人发来的信息对收信者没有意义。

    • (2) 登录认证

    客户端需要将认证标识传送给服务器,此认证标识 (可能是一个随机数) 其它客户端可以知道,因此需要用私钥加密,客户端保存的是私钥。服务器端保存的是公钥,其它服务器知道公钥没有关系,因为客户端不需要登录其它服务器。

    • (3) 数字签名

    数字签名是为了表明信息没有受到伪造,确实是信息拥有者发出来的,附在信息原文的后面。就像手写的签名一样,具有不可抵赖性和简洁性。

    简洁性:对信息原文做哈希运算,得到消息摘要,信息越短加密的耗时越少。

    不可抵赖性:信息拥有者要保证签名的唯一性,必须是唯一能够加密消息摘要的人,因此必须用私钥加密 (就像字迹他人无法学会一样),得到签名。如果用公钥,那每个人都可以伪造签名了。

    • (4) 数字证书

    问题起源:对1和3,发信者怎么知道从网上获取的公钥就是真的?没有遭受中间人攻击?

    这样就需要第三方机构来保证公钥的合法性,这个第三方机构就是 CA (Certificate Authority),证书中心。

    CA 用自己的私钥对信息原文所有者发布的公钥和相关信息进行加密,得出的内容就是数字证书。

    信息原文的所有者以后发布信息时,除了带上自己的签名,还带上数字证书,就可以保证信息不被篡改了。信息的接收者先用 CA给的公钥解出信息所有者的公钥,这样可以保证信息所有者的公钥是真正的公钥,然后就能通过该公钥证明数字签名是否真实了。

    二、RSA算法

    2.1 简介

    RSA 是目前最有影响力的公钥加密算法,该算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥。公钥是可发布的供任何人使用,私钥则为自己所有,供解密之用。

    2.2 工作流程

    A 要把信息发给 B 为例,确定角色:A 为加密者,B 为解密者。首先由 B 随机确定一个 KEY,称之为私钥,将这个 KEY 始终保存在机器 B 中而不发出来;然后,由这个 KEY 计算出另一个 KEY,称之为公钥。这个公钥的特性是几乎不可能通过它自身计算出生成它的私钥。接下来通过网络把这个公钥传给 A,A 收到公钥后,利用公钥对信息加密,并把密文通过网络发送到 B,最后 B 利用已知的私钥,就能对密文进行解码了。以上就是 RSA 算法的工作流程。

    2.3 运算速度

    由于进行的都是大数计算,使得 RSA 最快的情况也比 DES 慢上好几倍,无论是软件还是硬件实现。速度一直是 RSA 的缺陷。一般来说只用于少量数据加密。RSA 的速度是对应同样安全级别的对称密码算法的1/1000左右。

    比起 DES 和其它对称算法来说,RSA 要慢得多。实际上一般使用一种对称算法来加密信息,然后用 RSA 来加密比较短的公钥,然后将用 RSA 加密的公钥和用对称算法加密的消息发送给接收方。

    这样一来对随机数的要求就更高了,尤其对产生对称密码的要求非常高,否则的话可以越过 RSA 来直接攻击对称密码。

    2.4 公钥传递安全

    和其它加密过程一样,对 RSA 来说分配公钥的过程是非常重要的。分配公钥的过程必须能够抵挡中间人攻击。假设 A 交给 B 一个公钥,并使 B 相信这是A 的公钥,并且 C 可以截下 A 和 B 之间的信息传递,那么 C 可以将自己的公钥传给 B,B 以为这是 A 的公钥。C 可以将所有 B 传递给 A 的消息截下来,将这个消息用自己的密钥解密,读这个消息,然后将这个消息再用 A 的公钥加密后传给 A。理论上 A 和 B 都不会发现 C 在偷听它们的消息,今天人们一般用数字认证来防止这样的攻击。

    2.5 攻击

    (1) 针对 RSA 最流行的攻击一般是基于大数因数分解。1999年,RSA-155 (512 bits) 被成功分解,花了五个月时间(约8000 MIPS 年)和224 CPU hours 在一台有3.2G 中央内存的 Cray C916计算机上完成。

    RSA-158 表示如下:

    39505874583265144526419767800614481996020776460304936454139376051579355626529450683609727842468219535093544305870490251995655335710209799226484977949442955603= 3388495837466721394368393204672181522815830368604993048084925840555281177×  11658823406671259903148376558383270818131012258146392600439520994131344334162924536139
    

    2009年12月12日,编号为 RSA-768 (768 bits, 232 digits) 数也被成功分解。这一事件威胁了现通行的1024-bit 密钥的安全性,普遍认为用户应尽快升级到2048-bit 或以上。

    RSA-768表示如下:

    1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413= 3347807169895689878604416984821269081770479498371376856891  2431388982883793878002287614711652531743087737814467999489×  3674604366679959042824463379962795263227915816434308764267  6032283815739666511279233373417143396810270092798736308917
    

    (2) 秀尔算法
    量子计算里的秀尔算法能使穷举的效率大大的提高。由于 RSA 算法是基于大数分解 (无法抵抗穷举攻击),因此在未来量子计算能对 RSA 算法构成较大的威胁。一个拥有 N 量子位的量子计算机,每次可进行2^N 次运算,理论上讲,密钥为1024位长的 RSA 算法,用一台512量子比特位的量子计算机在1秒内即可破解。

    2.6 例子

    • 首先引入commons-codec
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.14</version>
    </dependency>
    
    • 密钥对对象
    /**
     * @author: huangyibo
     * @Date: 2022/4/29 18:47
     * @Description: 非对称加密 密钥对对象
     */
    
    public class RsaKeyPair {
    
        private String publicKey;
    
        private String privateKey;
    
        public RsaKeyPair(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }
    
        public String getPublicKey() {
            return publicKey;
        }
    
        public String getPrivateKey() {
            return privateKey;
        }
    }
    
    • RSA工具类
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Cipher;
    import java.security.*;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:03
     * @Description: RSA 非对称加密算法
     */
    
    public class RSAUtil {
    
        //RSA编码
        public static final String ALGORITHM = "RSA";
    
        /**
         * 默认种子
         */
        private static final String DEFAULT_SEED = "0f22507a10bbddd07d8a3082122966e3";
    
        /**
         * 构建RSA密钥对
         * @return
         * @throws NoSuchAlgorithmException
         */
        public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
            // 初始化随机产生器
            SecureRandom secureRandom = new SecureRandom();
            secureRandom.setSeed(DEFAULT_SEED.getBytes());
            keyPairGenerator.initialize(1024, secureRandom);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
            String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
            return new RsaKeyPair(publicKeyString, privateKeyString);
        }
    
    
        /**
         * 公钥解密
         *
         * @param publicKeyText 公钥
         * @param text          加密字符串
         * @return              明文
         * @throws Exception    解密过程中的异常信息
         */
        public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(Base64.decodeBase64(text));
            return new String(result);
        }
    
        /**
         * 私钥加密
         *
         * @param privateKeyText    私钥
         * @param text              加密数据
         * @return                  密文
         * @throws Exception        加密过程中的异常信息
         */
        public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(text.getBytes());
            return Base64.encodeBase64String(result);
        }
    
    
        /**
         * 私钥解密
         *
         * @param privateKeyText    私钥
         * @param text              加密字符串
         * @return                  明文
         * @throws Exception        解密过程中的异常信息
         */
        public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(Base64.decodeBase64(text));
            return new String(result);
        }
    
    
        /**
         * 公钥加密
         * @param publicKeyText     公钥
         * @param text              加密字符串
         * @return                  密文
         * @throws Exception        加密过程中的异常信息
         */
        public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
            X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(text.getBytes());
            return Base64.encodeBase64String(result);
        }
    }
    
    • RSA测试
    /**
     * @author: huangyibo
     * @Date: 2022/4/29 18:57
     * @Description:
     */
    
    public class RSATest {
    
        private static final String src = "abcdefghijklmnopqrstuvwxyz";
    
        public static void main(String[] args) throws Exception {
            System.out.println("\n");
            RsaKeyPair rsaKeyPair = RSAUtil.generateKeyPair();
            System.out.println("公钥:" + rsaKeyPair.getPublicKey());
            System.out.println("私钥:" + rsaKeyPair.getPrivateKey());
            System.out.println("\n");
            test1(rsaKeyPair, src);
            System.out.println("\n");
            test2(rsaKeyPair, src);
            System.out.println("\n");
        }
    
        /**
         * 公钥加密私钥解密
         */
        private static void test1(RsaKeyPair keyPair, String source) throws Exception {
            System.out.println("***************** 公钥加密私钥解密开始 *****************");
            String text1 = RSAUtil.encryptByPublicKey(keyPair.getPublicKey(), source);
            String text2 = RSAUtil.decryptByPrivateKey(keyPair.getPrivateKey(), text1);
            System.out.println("加密前:" + source);
            System.out.println("加密后:" + text1);
            System.out.println("解密后:" + text2);
            if (source.equals(text2)) {
                System.out.println("解密字符串和原始字符串一致,解密成功");
            } else {
                System.out.println("解密字符串和原始字符串不一致,解密失败");
            }
            System.out.println("***************** 公钥加密私钥解密结束 *****************");
        }
    
        /**
         * 私钥加密公钥解密
         *
         * @throws Exception
         */
        private static void test2(RsaKeyPair keyPair, String source) throws Exception {
            System.out.println("***************** 私钥加密公钥解密开始 *****************");
            String text1 = RSAUtil.encryptByPrivateKey(keyPair.getPrivateKey(), source);
            String text2 = RSAUtil.decryptByPublicKey(keyPair.getPublicKey(), text1);
            System.out.println("加密前:" + source);
            System.out.println("加密后:" + text1);
            System.out.println("解密后:" + text2);
            if (source.equals(text2)) {
                System.out.println("解密字符串和原始字符串一致,解密成功");
            } else {
                System.out.println("解密字符串和原始字符串不一致,解密失败");
            }
            System.out.println("***************** 私钥加密公钥解密结束 *****************");
        }
    }
    

    三、DSA算法

    3.1 简介

    DSA (Digital Signature Algorithm) 是 Schnorr 和 ElGamal 签名算法的变种,被美国 NIST 作为 DSS (DigitalSignature Standard)。 DSA 是基于整数有限域离散对数难题的。

    简单的说,这是一种更高级的验证方式,用作数字签名。不单单只有公钥、私钥,还有数字签名。私钥加密生成数字签名,公钥验证数据及签名,如果数据和签名不匹配则认为验证失败。数字签名的作用就是校验数据在传输过程中不被修改,数字签名,是单向加密的升级。

    3.2 处理过程

    • (1) 使用消息摘要算法将发送数据加密生成数字摘要。
    • (2) 发送方用自己的私钥对摘要再加密,形成数字签名。
    • (3) 将原文和加密的摘要同时传给对方。
    • (4) 接受方用发送方的公钥对摘要解密,同时对收到的数据用消息摘要算法产生同一摘要。
    • (5) 将解密后的摘要和收到的数据在接收方重新加密产生的摘要相互对比,如果两者一致,则说明在传送过程中信息没有破坏和篡改。否则,则说明信息已经失去安全性和保密性。

    3.3 例子

    • 非对称加密算法基类
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:03
     * @Description: 非对称加密算法基类
     */
    
    public class EncryptAlgorithmBase {
    
        /**
         * 默认种子
         */
        private static final String DEFAULT_SEED = "0f22507a10bbddd07d8a3082122966e3";
    
    
        /**
         * 生成密钥实际方法,可以使用多种方式
         * 提供以下多种方式
         * { "DSA", "SHA1withDSA", "1024" }, { "DSA", "SHA256withDSA", "1024" },
         * { "DSA", "SHA256withDSA", "2048" }, { "RSA", "SHA256withRSA", "1024" },
         * { "RSA", "SHA256withRSA", "2048" }, { "RSA", "SHA256withRSA", "3192" },
         * { "RSA", "SHA512withRSA", "1024" }, { "RSA", "SHA512withRSA", "2048" },
         * { "RSA", "SHA512withRSA", "3192" }, { "RSA", "MD5withRSA", "1024" },
         * { "RSA", "MD5withRSA", "2048" },
         * { "RSA", "MD5withRSA", "3192" }, { "EC", "SHA1withECDSA", "128" },
         * { "EC", "SHA1withECDSA", "256" },
         * { "EC", "SHA256withECDSA", "128" }, { "EC", "SHA256withECDSA", "256" },
         * { "EC", "SHA512withECDSA", "128" }, { "EC", "SHA512withECDSA", "256" },
         *
         * @param algorithm
         * @param bit
         * @return
         * @throws Exception
         */
        protected static RsaKeyPair generateKeyPair(String algorithm, int bit) throws Exception {
            //KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
            //初始化密钥对生成器,密钥大小为96-1024位
            keyPairGenerator.initialize(bit, new SecureRandom());
            // 生成一个密钥对,保存在keyPair中
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            String publicKey = ByteUtils.bytesToHexString(keyPair.getPublic().getEncoded());
            String privateKey = ByteUtils.bytesToHexString(keyPair.getPrivate().getEncoded());
            return new RsaKeyPair(publicKey, privateKey);
        }
    
    
        /**
         * 非对称加密签名 - 私钥加密
         * @param str
         * @param privateKey
         * @param algorithm
         * @param signAlgorithm
         * @return
         * @throws Exception
         */
        public static String sign(String str, String privateKey, String algorithm, String signAlgorithm) throws Exception {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(ByteUtils.hexStringToBytes(privateKey));
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            PrivateKey dsaPrivateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Signature signature = Signature.getInstance(signAlgorithm);
            signature.initSign(dsaPrivateKey);
            signature.update(str.getBytes());
            return ByteUtils.bytesToHexString(signature.sign());
        }
    
    
        /**
         * 非对称加密验证 - 公钥验证
         * @param sign
         * @param str
         * @param publicKey
         * @param algorithm
         * @param signAlgorithm
         * @return
         * @throws Exception
         */
        public static boolean verify(String sign, String str, String publicKey,String algorithm,String signAlgorithm) throws Exception {
            //base64编码的公钥
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(ByteUtils.hexStringToBytes(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            PublicKey dsaPublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signature = Signature.getInstance(signAlgorithm);
            signature.initVerify(dsaPublicKey);
            signature.update(str.getBytes());
            return signature.verify(ByteUtils.hexStringToBytes(sign));
        }
    }
    
    • 对称加密 密钥对对象
    /**
     * @author: huangyibo
     * @Date: 2022/4/29 18:47
     * @Description: 非对称加密 密钥对对象
     */
    
    public class RsaKeyPair {
    
        private String publicKey;
    
        private String privateKey;
    
        public RsaKeyPair(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }
    
        public String getPublicKey() {
            return publicKey;
        }
    
        public String getPrivateKey() {
            return privateKey;
        }
    }
    
    • DSA 加解密实现
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:52
     * @Description:  DSA 加解密实现
     */
    
    public class DSAUtils extends EncryptAlgorithmBase {
    
        //字符编码
        public static final String ALGORITHM = "DSA";
    
        public static final String SIGN_ALGORITHM = "SHA1withDSA";
    
    
        /**
         * DSA 验签
         *
         * @param str       加密字符串
         * @param publicKey 公钥
         * @return 密文
         * @throws Exception 加密过程中的异常信息
         */
        public static boolean verify(String sign, String str, String publicKey) throws Exception {
            return verify(sign, str, publicKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        /**
         * DSA 签名
         *
         * @param str        加密字符串
         * @param privateKey 私钥
         * @return 铭文
         * @throws Exception 解密过程中的异常信息
         */
        public static String sign(String str, String privateKey) throws Exception {
            return sign(str, privateKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        public static void main(String[] args) throws Exception {
            RsaKeyPair rsaKeyPair = generateKeyPair(ALGORITHM, 1024);
            System.out.println(rsaKeyPair.getPublicKey());
            System.out.println(rsaKeyPair.getPrivateKey());
            String message = "我要测试DSA";
            String sign = sign(message, rsaKeyPair.getPrivateKey());
            System.out.println(verify(sign, message, rsaKeyPair.getPublicKey()));
        }
    }
    
    • RSA 加解密实现
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:52
     * @Description:  RSA 加解密实现
     */
    
    public class RSAUtils extends EncryptAlgorithmBase {
    
        //字符编码
        public static final String ALGORITHM = "RSA";
    
        public static final String SIGN_ALGORITHM = "SHA512withRSA";
    
    
        /**
         * RSA 验签
         *
         * @param str       加密字符串
         * @param publicKey 公钥
         * @return 密文
         * @throws Exception 加密过程中的异常信息
         */
        public static boolean verify(String sign, String str, String publicKey) throws Exception {
            return verify(sign, str, publicKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        /**
         * RSA 签名
         *
         * @param str        加密字符串
         * @param privateKey 私钥
         * @return 铭文
         * @throws Exception 解密过程中的异常信息
         */
        public static String sign(String str, String privateKey) throws Exception {
            return sign(str, privateKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        public static void main(String[] args) throws Exception {
            RsaKeyPair rsaKeyPair = generateKeyPair(ALGORITHM, 1024);
            System.out.println(rsaKeyPair.getPublicKey());
            System.out.println(rsaKeyPair.getPrivateKey());
            String message = "我要测试DSA";
            String sign = sign(message, rsaKeyPair.getPrivateKey());
            System.out.println(verify(sign, message, rsaKeyPair.getPublicKey()));
        }
    }
    
    • ECDSA签名算法
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:52
     * @Description:  ECDSA签名算法
     */
    
    public class ECDSAUtils extends EncryptAlgorithmBase {
    
        //字符编码
        public static final String ALGORITHM = "EC";
    
        public static final String SIGN_ALGORITHM = "SHA256withECDSA";
    
    
        /**
         * ECDSA 验签
         *
         * @param str       加密字符串
         * @param publicKey 公钥
         * @return 密文
         * @throws Exception 加密过程中的异常信息
         */
        public static boolean verify(String sign, String str, String publicKey) throws Exception {
            return verify(sign, str, publicKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        /**
         * ECDSA 签名
         *
         * @param str        加密字符串
         * @param privateKey 私钥
         * @return 铭文
         * @throws Exception 解密过程中的异常信息
         */
        public static String sign(String str, String privateKey) throws Exception {
            return sign(str, privateKey, ALGORITHM, SIGN_ALGORITHM);
        }
    
    
        public static void main(String[] args) throws Exception {
            RsaKeyPair rsaKeyPair = generateKeyPair(ALGORITHM, 256);
            System.out.println(rsaKeyPair.getPublicKey());
            System.out.println(rsaKeyPair.getPrivateKey());
            String message = "我要测试DSA";
            String sign = sign(message, rsaKeyPair.getPrivateKey());
            System.out.println(verify(sign, message, rsaKeyPair.getPublicKey()));
        }
    }
    
    • 基本数据类型转换
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:47
     * @Description: 基本数据类型转换(主要是byte和其它类型之间的互转)
     */
    
    public class ByteUtils {
    
        /**
         *
         * <pre>
         * 将4个byte数字组成的数组合并为一个float数.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static float byte4ToFloat(byte[] arr) {
            if (arr == null || arr.length != 4) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是4位!");
            }
            int i = byte4ToInt(arr);
            return Float.intBitsToFloat(i);
        }
    
        /**
         *
         * <pre>
         * 将一个float数字转换为4个byte数字组成的数组.
         * </pre>
         *
         * @param f
         * @return
         */
        public static byte[] floatToByte4(float f) {
            int i = Float.floatToIntBits(f);
            return intToByte4(i);
        }
    
        /**
         *
         * <pre>
         * 将八个byte数字组成的数组转换为一个double数字.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static double byte8ToDouble(byte[] arr) {
            if (arr == null || arr.length != 8) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是8位!");
            }
            long l = byte8ToLong(arr);
            return Double.longBitsToDouble(l);
        }
    
        /**
         *
         * <pre>
         * 将一个double数字转换为8个byte数字组成的数组.
         * </pre>
         *
         * @param i
         * @return
         */
        public static byte[] doubleToByte8(double i) {
            long j = Double.doubleToLongBits(i);
            return longToByte8(j);
        }
    
        /**
         *
         * <pre>
         * 将一个char字符转换为两个byte数字转换为的数组.
         * </pre>
         *
         * @param c
         * @return
         */
        public static byte[] charToByte2(char c) {
            byte[] arr = new byte[2];
            arr[0] = (byte) (c >> 8);
            arr[1] = (byte) (c & 0xff);
            return arr;
        }
    
        /**
         *
         * <pre>
         * 将2个byte数字组成的数组转换为一个char字符.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static char byte2ToChar(byte[] arr) {
            if (arr == null || arr.length != 2) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是2位!");
            }
            return (char) (((char) (arr[0] << 8)) | ((char) arr[1]));
        }
    
        /**
         *
         * <pre>
         * 将一个16位的short转换为长度为2的8位byte数组.
         * </pre>
         *
         * @param s
         * @return
         */
        public static byte[] shortToByte2(Short s) {
            byte[] arr = new byte[2];
            arr[0] = (byte) (s >> 8);
            arr[1] = (byte) (s & 0xff);
            return arr;
        }
    
        /**
         *
         * <pre>
         * 长度为2的8位byte数组转换为一个16位short数字.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static short byte2ToShort(byte[] arr) {
            if (arr != null && arr.length != 2) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是2位!");
            }
            return (short) (((short) arr[0] << 8) | ((short) arr[1] & 0xff));
        }
    
        /**
         *
         * <pre>
         * 将short转换为长度为16的byte数组.
         * 实际上每个8位byte只存储了一个0或1的数字
         * 比较浪费.
         * </pre>
         *
         * @param s
         * @return
         */
        public static byte[] shortToByte16(short s) {
            byte[] arr = new byte[16];
            for (int i = 15; i >= 0; i--) {
                arr[i] = (byte) (s & 1);
                s >>= 1;
            }
            return arr;
        }
    
        public static short byte16ToShort(byte[] arr) {
            if (arr == null || arr.length != 16) {
                throw new IllegalArgumentException("byte数组必须不为空,并且长度为16!");
            }
            short sum = 0;
            for (int i = 0; i < 16; ++i) {
                sum |= (arr[i] << (15 - i));
            }
            return sum;
        }
    
        /**
         *
         * <pre>
         * 将32位int转换为由四个8位byte数字.
         * </pre>
         *
         * @param sum
         * @return
         */
        public static byte[] intToByte4(int sum) {
            byte[] arr = new byte[4];
            arr[0] = (byte) (sum >> 24);
            arr[1] = (byte) (sum >> 16);
            arr[2] = (byte) (sum >> 8);
            arr[3] = (byte) (sum & 0xff);
            return arr;
        }
    
        /**
         * <pre>
         * 将长度为4的8位byte数组转换为32位int.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static int byte4ToInt(byte[] arr) {
            if (arr == null || arr.length != 4) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是4位!");
            }
            return (int) (((arr[0] & 0xff) << 24) | ((arr[1] & 0xff) << 16) | ((arr[2] & 0xff) << 8) | ((arr[3] & 0xff)));
        }
    
        /**
         *
         * <pre>
         * 将长度为8的8位byte数组转换为64位long.
         * </pre>
         *
         * 0xff对应16进制,f代表1111,0xff刚好是8位 byte[]
         * arr,byte[i]&0xff刚好满足一位byte计算,不会导致数据丢失. 如果是int计算. int[] arr,arr[i]&0xffff
         *
         * @param arr
         * @return
         */
        public static long byte8ToLong(byte[] arr) {
            if (arr == null || arr.length != 8) {
                throw new IllegalArgumentException("byte数组必须不为空,并且是8位!");
            }
            return (long) (((long) (arr[0] & 0xff) << 56) | ((long) (arr[1] & 0xff) << 48) | ((long) (arr[2] & 0xff) << 40)
                    | ((long) (arr[3] & 0xff) << 32) | ((long) (arr[4] & 0xff) << 24)
                    | ((long) (arr[5] & 0xff) << 16) | ((long) (arr[6] & 0xff) << 8) | ((long) (arr[7] & 0xff)));
        }
    
        /**
         * 将一个long数字转换为8个byte数组组成的数组.
         */
        public static byte[] longToByte8(long sum) {
            byte[] arr = new byte[8];
            arr[0] = (byte) (sum >> 56);
            arr[1] = (byte) (sum >> 48);
            arr[2] = (byte) (sum >> 40);
            arr[3] = (byte) (sum >> 32);
            arr[4] = (byte) (sum >> 24);
            arr[5] = (byte) (sum >> 16);
            arr[6] = (byte) (sum >> 8);
            arr[7] = (byte) (sum & 0xff);
            return arr;
        }
    
        /**
         *
         * <pre>
         * 将int转换为32位byte.
         * 实际上每个8位byte只存储了一个0或1的数字
         * 比较浪费.
         * </pre>
         *
         * @param num
         * @return
         */
        public static byte[] intToByte32(int num) {
            byte[] arr = new byte[32];
            for (int i = 31; i >= 0; i--) {
                // &1 也可以改为num&0x01,表示取最地位数字.
                arr[i] = (byte) (num & 1);
                // 右移一位.
                num >>= 1;
            }
            return arr;
        }
    
        /**
         *
         * <pre>
         * 将长度为32的byte数组转换为一个int类型值.
         * 每一个8位byte都只存储了0或1的数字.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static int byte32ToInt(byte[] arr) {
            if (arr == null || arr.length != 32) {
                throw new IllegalArgumentException("byte数组必须不为空,并且长度是32!");
            }
            int sum = 0;
            for (int i = 0; i < 32; ++i) {
                sum |= (arr[i] << (31 - i));
            }
            return sum;
        }
    
        /**
         *
         * <pre>
         * 将长度为64的byte数组转换为一个long类型值.
         * 每一个8位byte都只存储了0或1的数字.
         * </pre>
         *
         * @param arr
         * @return
         */
        public static long byte64ToLong(byte[] arr) {
            if (arr == null || arr.length != 64) {
                throw new IllegalArgumentException("byte数组必须不为空,并且长度是64!");
            }
            long sum = 0L;
            for (int i = 0; i < 64; ++i) {
                sum |= ((long) arr[i] << (63 - i));
            }
            return sum;
        }
    
        /**
         *
         * <pre>
         * 将一个long值转换为长度为64的8位byte数组.
         * 每一个8位byte都只存储了0或1的数字.
         * </pre>
         *
         * @param sum
         * @return
         */
        public static byte[] longToByte64(long sum) {
            byte[] arr = new byte[64];
            for (int i = 63; i >= 0; i--) {
                arr[i] = (byte) (sum & 1);
                sum >>= 1;
            }
            return arr;
        }
    
        /**
         * <pre>
         * 把byte[]转换成16进制进制字符串
         * </pre>
         * @param src
         * @return
         */
        public static String bytesToHexString(byte[] src){
            StringBuilder stringBuilder = new StringBuilder("");
            if (src == null || src.length <= 0) {
                return null;
            }
            for (int i = 0; i < src.length; i++) {
                int v = src[i] & 0xFF;
                String hv = Integer.toHexString(v);
                if (hv.length() < 2) {
                    stringBuilder.append(0);
                }
                stringBuilder.append(hv);
            }
            return stringBuilder.toString();
        }
    
        /**
         * <pre>
         * 把16进制进制字符串转换成byte[]
         * </pre>
         * @param hexString
         * @return
         */
        public static byte[] hexStringToBytes(String hexString) {
            if (hexString == null || hexString.equals("")) {
                return null;
            }
            int length = hexString.length() / 2;
            char[] hexChars = hexString.toCharArray();
            byte[] d = new byte[length];
            for (int i = 0; i < length; i++) {
                int pos = i * 2;
                d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }
            return d;
        }
    
    
        /**
         * 将16进制字符转换为字节
         * @param c
         * @return
         */
        private static byte charToByte(char c) {
            return (byte) "0123456789abcdef".indexOf(c);
        }
    
        /**
         * byte[]转换成bit
         * @param bytes
         * @return
         */
        public static String bytesToBits(byte[] bytes) {
            StringBuilder sb = new StringBuilder();
            for(byte b:bytes){
                sb.append(byteToBits(b));
            }
            return sb.toString();
        }
    
        /**
         * byte转换成8位bit
         * @param b
         * @return
         */
        public static String byteToBits(byte b) {
            int z = b; z |= 256;
            String str = Integer.toBinaryString(z);
            int len = str.length();
            return str.substring(len-8, len);
        }
    
        /**
         * 计算校验和
         * @param bytes
         * @return
         */
        public static final int calculateCheckSum(byte[] bytes) {
            int sum = 0;
            for (byte b : bytes) {
                sum += (short)b;
            }
            return sum > 65535 ? (sum-65535) : sum;
        }
    }
    
    /**
     * @author: huangyibo
     * @Date: 2022/4/28 18:20
     * @Description: GZip 转化的工具类
     */
    
    public class GZipUtils {
    
        public final static int GZIP_MAGIC = 0x8b1f;
    
        private static final Logger logger = LoggerFactory.getLogger(GZipUtils.class);
    
        /**
         * 从输入流中构建消息
         * @param is 输入流
         * @throws IOException 读流失败则抛出异常
         */
        public static byte[]  buildMsg(InputStream is) throws  Exception {
            byte[] messages=null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
            byte[] receiveBuf = new byte[1024];
            int len=0;
            while ((len = is.read(receiveBuf)) != -1) {
                try {
                    baos.write(receiveBuf, 0, len);
                } catch (Exception e) {
                    logger.error("Fail to do message check sum!", e);
                    throw e;
                }
            }
            if (baos.size() == 0) { // 判断是否有消息数�?
                return null;
            }
            messages = baos.toByteArray();
            try {
                baos.close();
            } catch (IOException e) {
                logger.error("Fail to close byte array output stream!", e);
                throw e;
            }
            if(GZipUtils.checkIfGzip(messages)) {
                try {
                    return GZipUtils.unGZip(messages);
                } catch (Exception e) {
                    return messages;
                }
            }
            return messages;
        }
    
        /**
         * gZip解压方法
         *
         * @throws Exception
         */
        public static byte[] unGZip(byte[] data) throws Exception {
            byte[] b = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            GZIPInputStream gzip =null;
            ByteArrayInputStream bis =null;
            try {
                bis = new ByteArrayInputStream(data);
                gzip = new GZIPInputStream(bis);
                byte[] buf = new byte[1024 * 1024];
                int num = -1;
                while ((num = gzip.read(buf, 0, buf.length)) != -1) {
                    baos.write(buf, 0, num);
                }
                b = baos.toByteArray();
            } catch (Throwable ex) {
                throw new Exception("UNGzip the byte [] error,please check the data format", ex);
            }finally {
                closeStream(baos);
                closeStream(gzip);
                closeStream(bis);
            }
            return b;
        }
    
        /**
         * 关闭流
         * @param clo
         */
        private static void closeStream(Closeable clo) {
            if(null!=clo) {
                try {
                    clo.close();
                } catch (Exception e) {
                    logger.error("Fail to close Stream!", e);
                }
            }
        }
    
        /**
         * 数据压缩
         *
         * @param is    输入流
         * @param os    输出流
         * @throws Exception
         */
        public static void compress(InputStream is, OutputStream os) throws Exception {
            GZIPOutputStream gos = new GZIPOutputStream(os);
            int count;
            byte[] data = new byte[1024];
            while ((count = is.read(data, 0, 1024)) != -1) {
                gos.write(data, 0, count);
            }
            gos.finish();
            gos.flush();
            gos.close();
        }
    
        /**
         * 数据压缩
         *
         * @param data
         * @return
         * @throws Exception
         */
        public static byte[] compress(byte[] data) throws Exception {
            ByteArrayInputStream bais = new ByteArrayInputStream(data);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 压缩
            compress(bais, baos);
            byte[] output = baos.toByteArray();
            baos.flush();
            baos.close();
            bais.close();
            return output;
        }
    
        private static int readUByte(byte by) {
            int b = by  & 0xFF;
            if (b <= -1 || b > 255) {
                return -1;
            }
            return b;
        }
    
        public static boolean checkIfGzip(byte[] bytes){
            if(null==bytes || bytes.length<3) {
                return false;
            }
            if(readUByte(bytes[2])!=8){
                return false;
            }
            int b =readUByte(bytes[0] );
            int b2 =readUByte(bytes[1] );
            if (b == -1 || b2 == -1) {
                return false;
            }
            int res=(b2<< 8) | b;
            if(res!= GZIP_MAGIC){
                return false;
            }
            return true;
        }
    
    
        private static int readUByte(InputStream in) throws IOException {
            int b = in.read();
            if (b == -1) {
                throw new EOFException();
            }
            if (b < -1 || b > 255) {
                throw new IOException( ".read() returned value out of range -1..255: " + b);
            }
            System.out.println(b);
            return b;
        }
        private static int readUShort(InputStream in) throws IOException {
            int b = readUByte(in);
            return (readUByte(in) << 8) | b;
        }
    }
    

    RSA、DSA总结:

    非对称性加密还有很多,RSA和DSA是比较常用和常见的加密方式,安全性来讲两者差不多,DSA只是一种算法,和RSA不同之处在于它不能用作加密和解密,也不能进行密钥交换,只用于签名,它比RSA要快很多,RSA啥都好,但是RSA算法的秘钥很长,加密的计算量比较大,安全性较高,但是加密速度比较慢,所以RSA加密常用于少量的核心数据的加密。

    四、ECC算法

    4.1 简介

    椭圆加密算法(ECC)是一种公钥加密算法,最初由 Koblitz 和 Miller 两人于1985年提出,其数学基础是利用椭圆曲线上的有理点构成 Abel 加法群上椭圆离散对数的计算困难性。公钥密码体制根据其所依据的难题一般分为三类:大整数分解问题类、离散对数问题类、椭圆曲线类。有时也把椭圆曲线类归为离散对数类。

    ECC 的主要优势是在某些情况下它比其他的方法使用更小的密钥 (比如 RSA),提供相当的或更高等级的安全。ECC 的另一个优势是可以定义群之间的双线性映射,基于 Weil 对或是 Tate 对;双线性映射已经在密码学中发现了大量的应用,例如基于身份的加密。不过一个缺点是加密和解密操作的实现比其他机制花费的时间长。

    ECC 被广泛认为是在给定密钥长度的情况下,最强大的非对称算法,因此在对带宽要求十分紧的连接中会十分有用。

    比特币钱包公钥的生成使用了椭圆曲线算法,通过椭圆曲线乘法可以从私钥计算得到公钥, 这是不可逆转的过程。

    4.2 优势

    • (1) 安全性高,有研究表示160位的椭圆密钥与1024位的 RSA 密钥安全性相同。

    • (2) 处理速度快,在私钥的加密解密速度上,ECC 算法比 RSA、DSA 速度更快,存储空间占用小,带宽要求低。

    4.3 例子

    https://github.com/esxgx/easy-ecc

    Java 中 Chipher、Signature、KeyPairGenerator、KeyAgreement、SecretKey 均不支持 ECC 算法。

    4.4 ECC算法详解:

    https://www.jianshu.com/p/58c1750c6f22

    五、DH算法

    5.1 简介

    DH,全称为"Diffie-Hellman",它是一种确保共享 KEY 安全穿越不安全网络的方法,也就是常说的密钥一致协议。由公开密钥密码体制的奠基人 Diffie 和 Hellman 所提出的一种思想。简单的说就是允许两名用户在公开媒体上交换信息以生成"一致"的、可以共享的密钥。也就是由甲方产出一对密钥 (公钥、私钥),乙方依照甲方公钥产生乙方密钥对 (公钥、私钥)。

    以此为基线,作为数据传输保密基础,同时双方使用同一种对称加密算法构建本地密钥 (SecretKey) 对数据加密。这样,在互通了本地密钥 (SecretKey) 算法后,甲乙双方公开自己的公钥,使用对方的公钥和刚才产生的私钥加密数据,同时可以使用对方的公钥和自己的私钥对数据解密。不单单是甲乙双方两方,可以扩展为多方共享数据通讯,这样就完成了网络交互数据的安全通讯。

    • 5.2 例子

    具体例子可以移步到这篇文章:非对称密码之DH密钥交换算法

    参考:
    https://blog.csdn.net/u014294681/article/details/86705999

    https://www.cnblogs.com/wangzxblog/p/13667634.html

    https://www.cnblogs.com/taoxw/p/15837729.html

    https://www.cnblogs.com/fangfan/p/4086662.html

    https://www.cnblogs.com/utank/p/7877761.html

    https://blog.csdn.net/m0_59133441/article/details/122686815

    https://www.cnblogs.com/muliu/p/10875633.html

    https://www.cnblogs.com/wf-zhang/p/14923279.html

    https://www.jianshu.com/p/7a927db713e4

    https://blog.csdn.net/ljx1400052550/article/details/79587133

    https://blog.csdn.net/yuanjian0814/article/details/109815473

    相关文章

      网友评论

          本文标题:非对称加密算法 (RSA、DSA、ECC、DH)

          本文链接:https://www.haomeiwen.com/subject/abidyrtx.html