1. 简介

        公开密钥密码学(英语:Public-key cryptography)也称非对称式密码学(英语:Asymmetric cryptography)是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;公钥用作加密,私钥则用作解密。使用公钥把明文加密后所得的密文,只能用相对应的私钥才能解密并得到原本的明文,最初用来加密的公钥不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。公钥可以公开,可任意向外发布;私钥不可以公开,必须由用户自行严格秘密保管,绝不透过任何途径向任何人提供,也不会透露给被信任的要通信的另一方。

        基于公开密钥加密的特性,它还能提供数字签名的功能,使电子文件可以得到如同在纸本文件上亲笔签署的效果。公开密钥基础建设透过信任数字证书认证机构的根证书、及其使用公开密钥加密作数字签名核发的公开密钥认证,形成信任链架构,已在TLS实现并在万维网的HTTP以HTTPS、在电子邮件的SMTP以SMTPS或STARTTLS引入。

        在现实世界上可作比拟的例子是,一个传统保管箱,开门和关门都是使用同一条钥匙,这是对称加密;而一个公开的邮箱,投递口是任何人都可以寄信进去的,这可视为公钥;而只有信箱主人拥有钥匙可以打开信箱,这就视为私钥。

        非对称加密过程:

Java生成EC公钥和密钥 java公钥私钥加密_DH

        此流程图显示非对称加密过程是单向的,其中一条密钥加密后只能用相对应的另一条密钥解密。 

2. 非对称加密算法--DH(密钥交换)

密钥长度

默认

工作模式

填充方式

实现方

512~1024(64倍数)

1024



JDK

        DH加解密应用:   

package com.bity.dh;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;

import static java.lang.System.*;

/**
 * <p>Title: JdkDh</p >
 * <p>Description: DH非对称算法实现 </p >
 * <p>Company: http://www.agree.com</p >
 * <p>Project: security</p >
 *
 * @author <a href="mailto:weiqi@agree.com.cn">WEIQI</a>
 * @version 1.0
 * @date 2022-04-27 19:51
 */
public class JdkDh {
    
    private static final String SRC = "I'm DH encryption algorithm";
    
    public static void main(String[] args) {
        // 解决 Unsupported secret key algorithm: DES 异常
        System.getProperties().setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");
        jdkDh();
    }
    
    private static void jdkDh() {
        try {
            // 初始化发送方密钥
            KeyPairGenerator senderKeyPairGenerator = KeyPairGenerator.getInstance("DH");
            senderKeyPairGenerator.initialize(512);
            KeyPair senderKeyPair = senderKeyPairGenerator.generateKeyPair();
            // 发送方密钥,发送给接收方(可以通过文件、优盘、网络等...)
            byte[] senderPublicKeyEnc = senderKeyPair.getPublic().getEncoded();
            
            // 初始化接收方密钥,注意在实际环境中接收方和发送方肯定不会在一个函数中
            KeyFactory keyFactory = KeyFactory.getInstance("DH");
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(senderPublicKeyEnc);
            PublicKey receiverPublicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            DHParameterSpec dhParameterSpec = ((DHPublicKey)receiverPublicKey).getParams();
            KeyPairGenerator receiverKeyPairGenerator = KeyPairGenerator.getInstance("DH");
            receiverKeyPairGenerator.initialize(dhParameterSpec);
            KeyPair receiverKeyPair = receiverKeyPairGenerator.generateKeyPair();
            PrivateKey receiverPrivateKey = receiverKeyPair.getPrivate();
            byte[] receiverPublicKeyEnc = receiverKeyPair.getPublic().getEncoded();
            
            // 构建密钥
            KeyAgreement receiverKeyAgreement = KeyAgreement.getInstance("DH");
            receiverKeyAgreement.init(receiverPrivateKey);
            receiverKeyAgreement.doPhase(receiverPublicKey, true);
            SecretKey receiverSecretKey = receiverKeyAgreement.generateSecret("DES");
            
            KeyFactory senderKeyFactory = KeyFactory.getInstance("DH");
            x509EncodedKeySpec = new X509EncodedKeySpec(receiverPublicKeyEnc);
            PublicKey senderPublicKey = senderKeyFactory.generatePublic(x509EncodedKeySpec);
            KeyAgreement senderKeyAgreement = KeyAgreement.getInstance("DH");
            senderKeyAgreement.init(senderKeyPair.getPrivate());
            senderKeyAgreement.doPhase(senderPublicKey, true);
            SecretKey senderSecretKey = senderKeyAgreement.generateSecret("DES");
            
            if (Objects.equals(receiverSecretKey, senderSecretKey)) {
                out.println("双方密钥相同");
            }
            
            // 加密
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.ENCRYPT_MODE, senderSecretKey);
            byte[] result = cipher.doFinal(SRC.getBytes(StandardCharsets.UTF_8));
            out.println("jdk dh encryption is : " + Base64.encodeBase64String(result));
            
            // 解密
            cipher.init(Cipher.DECRYPT_MODE, receiverSecretKey);
            result = cipher.doFinal(result);
            out.println("jdk dh decrypt is : " + new String(result));
            
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
    }
}

        运行结果:

双方密钥相同
jdk dh encryption is : Be3LeXqV/q1PDEbpH62LL129gaV5Og0Eo3GY9e00B4o=
jdk dh decrypt is : I'm DH encryption algorithm

        注意:由于JDK8 update 161之后,DH的密钥长度至少为512位,但DES算法密钥不能达到这样的长度,所以运行会抛出Unsupported secret key algorithm: DES异常。解决办法有两种如下:

        1. 在代码中加如下代码:

System.getProperties().setProperty("jdk.crypto.KeyAgreement.legacyKDF", "true");

        2. 在启动的时候添加JVM变量:-Djdk.crypto.KeyAgreement.legacyKDF=true

 

Java生成EC公钥和密钥 java公钥私钥加密_Java生成EC公钥和密钥_02

        DH数据加密传输图示: 

Java生成EC公钥和密钥 java公钥私钥加密_DH_03

3. 非对称加密算法--RSA

        RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的。当时他们三人都在麻省理工学院工作。RSA 就是他们三人姓氏开头字母拼在一起组成的。

        对极大整数做因数分解的难度决定了 RSA 算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA 算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用 RSA 加密的信息的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的 RSA 钥匙才可能被强力方式破解。到2020年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被破解的。

        RSA算法加密实现:

package com.bity.rsa;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import static java.lang.System.*;

/**
 * <p>Title: JdkRsa</p >
 * <p>Description: RSA非对称加密算法实现 </p >
 * <p>Company: http://www.agree.com</p >
 * <p>Project: security</p >
 *
 * @author <a href="mailto:weiqi@agree.com.cn">WEIQI</a>
 * @version 1.0
 * @date 2022-04-27 21:38
 */
public class JdkRsa {
    
    private static final String SRC = "I'm RSA encryption algorithm";
    
    public static void main(String[] args) {
        jdkRsa();
    }
    
    /**
     * JDK-RSA算法实现
     * 
     * @author: <a href="mailto:weiqi@agree.com.cn">WEIQI</a>
     * @date: 2022-04-28 0:15
     */
    private static void jdkRsa() {
        try {
            // 初始化密钥
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(512);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
            
            out.println("public key is : " + Base64.encodeBase64String(rsaPublicKey.getEncoded()));
            out.println("private key is : " + Base64.encodeBase64String(rsaPrivateKey.getEncoded()));
            
            // 私钥加密,公钥解密 -- 加密
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(SRC.getBytes(StandardCharsets.UTF_8));
            out.println("私钥加密,公钥解密 -- 加密 : " + Base64.encodeBase64String(result));
            
            // 私钥加密,公钥解密 -- 解密
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
            keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            result = cipher.doFinal(result);
            out.println("私钥加密,公钥解密 -- 解密 : " + new String(result));
    
            // 公钥加密,私钥解密 -- 加密
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] res = cipher.doFinal(SRC.getBytes(StandardCharsets.UTF_8));
            out.println("公钥加密,私钥解密 -- 加密 : " + Base64.encodeBase64String(res));
            // 公钥加密,私钥解密 -- 解密
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            res = cipher.doFinal(res);
            out.println("公钥加密,私钥解密 -- 解密 : " + new String(res));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
    }
}

        运行结果: 

public key is : MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIfDTXqCrjGHP9tCmLujavsngP8nqSHIl/JkFN8ZmQEcn48xzSXdijEG8Ssgm3SkDWICT0cW9wf3mZ+UkVxLx90CAwEAAQ==
private key is : MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAh8NNeoKuMYc/20KYu6Nq+yeA/yepIciX8mQU3xmZARyfjzHNJd2KMQbxKyCbdKQNYgJPRxb3B/eZn5SRXEvH3QIDAQABAkASyfa5E8jj1eICiE72+QDfTXJO3cBMiqRsyWkSD0rbmlL/Qv1xDDQonWM58sIR6DOBWQZ+uXbkL1VtOuZ9sgfBAiEA9IxhRwkTYA1GVUKmgZPX+7CsfUmIZi8P/r9/C29XQZUCIQCOHtBkIOupaTLPv3A4yznEidygfbL8nrLV2Xhf6wVrKQIhAJ+RdewPDPhw0QLTIaiNWrIdXv/FWl4quUolk/VXKl1dAiASpbpkGOmy7cmr9otr+EZZIlmfeT695LjEVGd19mlcmQIhAJfua+j3/PT0+z0nPIaGDvczviyl5SxmDo79rfMNpi10
私钥加密,公钥解密 -- 加密 : AiYjJSlGlz5x86mwVW/wjieG/uJsoLEqF+xRcPLzq2HL7yIrSrE3oT4wibrvUDR6kp37eHvFaAT+/wVYCreVvg==
私钥加密,公钥解密 -- 解密 : I'm RSA encryption algorithm
公钥加密,私钥解密 -- 加密 : Io2diORtKbeTz5eOOziHrqZKzS1K19U4t3YPydhM4w2LrzW7bJK/d4DQrWTBAhg8rt28OjbGcJfqd1w8MMy4iw==
公钥加密,私钥解密 -- 解密 : I'm RSA encryption algorithm

        从上面例子可以看出RSA算法是支持私钥加密、公钥解密和公钥加密、私钥解密的算法。RSA也是唯一被广泛接受并实现的非对称加密算法,从某种程度来说,RSA已经成为非对称加密算法的一个标准了。 

        RSA数据加密传输图示:

Java生成EC公钥和密钥 java公钥私钥加密_非对称加密_04

4. 非对称加密算法--EIGamal

        在密码学中,ElGamal加密算法是一个基于迪菲-赫尔曼密钥交换的非对称加密算法。它在1985年由塔希尔·盖莫尔提出。GnuPG和PGP等很多密码学系统中都应用到了ElGamal算法。

        ElGamal加密算法由三部分组成:密钥生成、加密和解密。

        ElGamal算法加解密应用:

package com.bity.eigamal;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.DHParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import static java.lang.System.out;

/**
 * <p>Title: BouncyCastleEiGamal</p >
 * <p>Description: ElGamal加密算法实现 </p >
 * <p>Company: http://www.agree.com</p >
 * <p>Project: security</p >
 *
 * @author <a href="mailto:weiqi@agree.com.cn">WEIQI</a>
 * @version 1.0
 * @date 2022-04-28 0:38
 */
public class BouncyCastleElGamal {
    
    private static final String SRC = "I'm ElGamal encryption algorithm";
    
    public static void main(String[] args) {
        bcElGamal();
    }
    
    private static void bcElGamal() {
        try {
            Security.addProvider(new BouncyCastleProvider());
            
            AlgorithmParameterGenerator algorithmParameterGenerator = AlgorithmParameterGenerator
                    .getInstance("ElGamal");
            algorithmParameterGenerator.init(256);
            AlgorithmParameters algorithmParameters = algorithmParameterGenerator.generateParameters();
            DHParameterSpec dhParameterSpec = algorithmParameters.getParameterSpec(DHParameterSpec.class);
            
            // 初始化密钥
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ElGamal");
            keyPairGenerator.initialize(dhParameterSpec, new SecureRandom());
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            PublicKey elGamalPublicKey = keyPair.getPublic();
            PrivateKey elGamalPrivateKey = keyPair.getPrivate();
            
            out.println("public key is : " + Base64.encodeBase64String(elGamalPublicKey.getEncoded()));
            out.println("private key is : " + Base64.encodeBase64String(elGamalPrivateKey.getEncoded()));
            
            // 公钥加密,私钥解密 -- 加密
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(elGamalPublicKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance("ElGamal");
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Cipher cipher = Cipher.getInstance("ElGamal");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(SRC.getBytes(StandardCharsets.UTF_8));
            
            out.println("私钥加密,公钥解密 -- 解密 : " + Base64.encodeBase64String(result));
            // 公钥加密,私钥解密 -- 解密
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(elGamalPrivateKey.getEncoded());
            keyFactory = KeyFactory.getInstance("ElGamal");
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            result = cipher.doFinal(result);
            out.println("私钥加密,公钥解密 -- 加密 : " + new String(result));
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidParameterSpecException | InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
    }
}

         运行结果:

public key is : MHYwTwYGKw4HAgEBMEUCIQCxvT1SNSrVl1/8KpvPhaE1MKiaXOHWk5SACDd39SfnHwIgQ5O8oKi9mIKhEDOCKwvYB+mozFgvmuoipw/ZDl6xCicDIwACIBh0K6bn2mtdvmiSMJj/HDlotAGOmTRFMZs+PFoIobQG
private key is : MHgCAQAwTwYGKw4HAgEBMEUCIQCxvT1SNSrVl1/8KpvPhaE1MKiaXOHWk5SACDd39SfnHwIgQ5O8oKi9mIKhEDOCKwvYB+mozFgvmuoipw/ZDl6xCicEIgIge9eM9ELM12n6bQKkj8iBtIXjvPeN5mwX9clNriqfSsA=
私钥加密,公钥解密 -- 解密 : e39h4LkKXQn1EIXdDkIxmWiB2pwgO5dZJcNfSmlXqS07SrW1VSLrx0L3RhdWm8hPLaqBZieR5quU/7oTzH/uuA==
私钥加密,公钥解密 -- 加密 : I'm ElGamal encryption algorithm

ElGamal数据加密传输图示:

Java生成EC公钥和密钥 java公钥私钥加密_RSA_05

 5. 总结

        对称密码是指在加密和解密时使用同一个密钥的方式,公钥密码则是指在加密和解密时使用不同密钥的方式。

        对称密钥加密牵涉到密钥管理的问题,尤其是密钥交换,它需要通信双方在通信之前先透过另一个安全的渠道交换共享的密钥,才可以安全地把密文透过不安全的渠道发送;对称密钥一旦被窃,其所作的加密将即时失效;而在互联网,如果通信双方分隔异地而素未谋面,则对称加密事先所需要的“安全渠道”变得不可行;非对称加密则容许加密公钥随便散布,解密的私钥不发往任何用户,只在单方保管;如此,即使公钥在网上被截获,如果没有与其匹配的私钥,也无法解密,极为适合在互联网上使用。

        另一方面,公钥解密的特性可以形成数字签名,使数据和文件受到保护并可信赖;如果公钥透过数字证书认证机构签授成为电子证书,更可作为数字身份的认证,这都是对称密钥加密无法实现的。不过,公钥加密在在计算上相当复杂,性能欠佳、远远不比对称加密;因此,在一般实际情况下,往往通过公钥加密来随机创建临时的对称秘钥,亦即对话键,然后才通过对称加密来传输大量、主体的数据。

        以上就是非对称加密算法的内容,在实际工作中可以根据具体业务的需求进行数据加密算法选择。