一、非对称加密

1、定义

非对称加密算法需要两个密钥:公开密钥(Public Key:简称公钥)和私有密钥(Private Key:简称私钥)。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

2、基本过程

甲方生成一对密钥并将公钥公开,需要向甲方发送信息的其他角色(乙方)使用该密钥(甲方的公钥)对机密信息进行加密后再发送给甲方;甲方再用自己私钥对加密后的信息进行解密。甲方想要回复乙方时正好相反,使用乙方的公钥对数据进行加密,同理,乙方使用自己的私钥来进行解密。


RSA公私钥对生成 Java rsa 公钥 私钥_密钥对

(图片来自网络)

3、签名

有了公私钥对,似乎解决了加密通信的难题。但是实际使用中又出问题了,那就是甲方收到消息后如何确认发信人是乙方而不是第三方呢?其实也很简单,只要发消息前多进行一次使用自己的私钥加密的过程就可以了,这次使用自己私钥加密信息的步骤就叫做签名。

私钥只有自己持有,公钥和私钥存在一一对应关系,即使用公钥只能解密出对应私钥加密的信息,因此就可以用私钥的加密过程当做验证身份的手段了。其实公钥、私钥加密数据的方法与原理都相同,只是按照用途分别命名了而已。

一般,公钥用来加密,私钥用来签名。

4、常见算法

常见的非对称加密算法有:RSA、ECC、Diffie-Hellman、El Gamal、DSA。其中,RSA算法使用最广泛。下面,介绍RSA算法的Java实现。

二、RSA算法实现

实现过程要用到Base64编码和解码,采用了Spring的工具类org.springframework.util.Base64Utils(可以调整为其他Base64工具类),完整实现见文末附录。

1、生成密钥对

具体实现: 

/**
 * 生成密钥对
 *
 * @return
 * @throws Exception
 */
public KeyPair genKeyPair() throws Exception {
    KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
    keyGen.initialize(512);
    return keyGen.generateKeyPair();
}

调用过程:

public static void main(String[] args) throws Exception {
    RSAUtil util = new RSAUtil();

    // 生成密钥对
    KeyPair keyPair = util.genKeyPair();

    // 打印密钥对字符串
    String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
    String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
    log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);
}

输出结果:

公钥: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKK9VZq5Apq0kPSKBTJUybUEa/5WkvrM6Y2E0P0lF3J4ujtcvAF6PSRW9hm2CyKpmHnYOuH9dgBy23Y3HyT8TIcCAwEAAQ==
私钥: MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAor1VmrkCmrSQ9IoFMlTJtQRr/laS+szpjYTQ/SUXcni6O1y8AXo9JFb2GbYLIqmYedg64f12AHLbdjcfJPxMhwIDAQABAkAZU70YZNW+bP6oSDix0hdISEVkYmXiiXSJtVNvKlAhXOAajnpDbTM3+mODpHq0NXrc9J26d3+E3pMI4Lcf4v4tAiEAy3yocAej0qJ5RK9NE4qCg1oeioMlMgt7nU+RBlW04KUCIQDMvLT0MZJHymAFbBEV/1TGICcN7Qn8yo7oU0GpgSEkuwIgTdvgxxzlPg8Uv4cjwrpYvdGZpf4QGVnzbnmnT/kzQFECIQDGhtG83Hio7n9Poquqte1BNRpJsal2nAAZHepU8CbwUwIhAJHDbY4+U42NzIcX9A56kx6u+Wk6jPgOiTFc0yYcWJmd

2、加密和解密

具体实现:

/**
 * 加密
 *
 * @param content   原始字符串
 * @param publicKey 公钥
 * @return 密文
 * @throws Exception
 */
public String encrypt(String content, PublicKey publicKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
    return new String(Base64Utils.encode(bytes));
}

/**
 * 解密
 *
 * @param content    密文字符串
 * @param privateKey 私钥
 * @return 明文
 * @throws Exception
 */
public String decrypt(String content, PrivateKey privateKey) throws Exception {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    byte[] bytes = cipher.doFinal(Base64Utils.decodeFromString(content));
    return new String(bytes);
}

调用过程:

public static void main(String[] args) throws Exception {
    RSAUtil util = new RSAUtil();

    // 生成密钥对
    KeyPair keyPair = util.genKeyPair();

    // 加密和解密
    String content = "Hello World!";
    String encryptContent = util.encrypt(content, keyPair.getPublic());
    String decryptContent = util.decrypt(encryptContent, keyPair.getPrivate());
    log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
}

输出结果:

原始字符串: Hello World!
加密后:L5hzF00QDSpMJse9nMTs0bl3yi/P2PJHO1u6nULzgTMhWVIrZuAMA0ISzb6eIaMFK/UsLzr4Dnh8TB0b1Y0XMA==
解密后:Hello World!

3、密钥对象

一般来说,密钥对是以字符串形式存储在配置文件中的,这就需要转换为对象,并重载一下加密和解密方法。

具体实现:

private PublicKey getPublicKey(String publicKey) throws Exception {
    byte[] keyBytes = Base64Utils.decode(publicKey.getBytes(StandardCharsets.UTF_8));

    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");

    return kf.generatePublic(keySpec);
}

private PrivateKey getPrivateKey(String privateKey) throws Exception {
    byte[] keyBytes = Base64Utils.decode(privateKey.getBytes(StandardCharsets.UTF_8));

    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");

    return kf.generatePrivate(keySpec);
}

方法重载:

/**
 * 加密
 *
 * @param content   原始字符串
 * @param publicKey 公钥
 * @return 密文
 * @throws Exception
 */
public String encrypt(String content, String publicKey) throws Exception {
    PublicKey key = this.getPublicKey(publicKey);
    return encrypt(content, key);
}

/**
 * 解密
 *
 * @param content    密文字符串
 * @param privateKey 私钥
 * @return 明文
 * @throws Exception
 */
public String decrypt(String content, String privateKey) throws Exception {
    PrivateKey key = this.getPrivateKey(privateKey);
    return decrypt(content, key);
}

调用过程:

public static void main(String[] args) throws Exception {
    RSAUtil util = new RSAUtil();

    // 生成密钥对
    KeyPair keyPair = util.genKeyPair();

    // 打印密钥对字符串
    String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
    String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
    log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);

    // 加密和解密
    String content = "Hello World!";
    String encryptContent = util.encrypt(content, publicKey);
    String decryptContent = util.decrypt(encryptContent, privateKey);
    log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
}

4、签名与验签(MD5)

常用签名有2种:MD5、SHA1,以下是MD5的具体实现:

/**
 * 签名
 *
 * @param content    待签名字符串(明文)
 * @param privateKey 密钥
 * @return 签名字符串(密文)
 * @throws Exception
 */
public String encodeMD5(String content, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("MD5withRSA");
    signature.initSign(privateKey);
    signature.update(content.getBytes(StandardCharsets.UTF_8));
    return new String(Base64Utils.encode(signature.sign()));
}

/**
 * 验签
 *
 * @param content   待验签字符串(密文)
 * @param sign      待比较的签名
 * @param publicKey 公钥
 * @return 验签结果
 * @throws Exception
 */
public boolean decodeMD5(String content, String sign, PublicKey publicKey) throws Exception {
    Signature signature = Signature.getInstance("MD5withRSA");
    signature.initVerify(publicKey);
    signature.update(content.getBytes(StandardCharsets.UTF_8));
    return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}

方法重载:

/**
 * 签名
 *
 * @param content    待签名字符串(明文)
 * @param privateKey 密钥
 * @return 签名字符串(密文)
 * @throws Exception
 */
public String encodeMD5(String content, String privateKey) throws Exception {
    PrivateKey key = this.getPrivateKey(privateKey);
    return encodeMD5(content, key);
}

/**
 * 验签
 *
 * @param content   待验签字符串(密文)
 * @param sign      待比较的签名
 * @param publicKey 公钥
 * @return 验签结果
 * @throws Exception
 */
public boolean decodeMD5(String content, String sign, String publicKey) throws Exception {
    PublicKey key = this.getPublicKey(publicKey);
    return decodeMD5(content, sign, key);
}

调用过程:

public static void main(String[] args) throws Exception {
    RSAUtil util = new RSAUtil();

    // 生成密钥对
    KeyPair keyPair = util.genKeyPair();

    // 签名与验签(MD5)
    String content2 = "lewis2951";
    String sign = util.encodeMD5(content2, privateKey);
    boolean verify = util.decodeMD5(content2, sign, publicKey);
    log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
}

重载的方法,调用也是一样的,这里暂略。

输出结果:

原始字符串:lewis2951
签名字符串:KhcflLlhHq1MV0qEKi3bdOTjoA+Ylj0yTfFeKf3kJtv6ZVKWeWTPtqgaccmUJrSmXgpwyS8zoVLkYZcgs9D3uA==
验签结果:true

5、签名与验签(SHA1)

以下是SHA1的具体实现:

/**
 * 签名
 *
 * @param content    待签名字符串(明文)
 * @param privateKey 密钥
 * @return 签名字符串(密文)
 * @throws Exception
 */
public String encodeSHA1(String content, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance("SHA1withRSA");
    signature.initSign(privateKey);
    signature.update(content.getBytes(StandardCharsets.UTF_8));
    return new String(Base64Utils.encode(signature.sign()));
}

/**
 * 验签
 *
 * @param content   待验签字符串(密文)
 * @param sign      待比较的签名
 * @param publicKey 公钥
 * @return 验签结果
 * @throws Exception
 */
public boolean decodeSHA1(String content, String sign, PublicKey publicKey) throws Exception {
    Signature signature = Signature.getInstance("SHA1withRSA");
    signature.initVerify(publicKey);
    signature.update(content.getBytes(StandardCharsets.UTF_8));
    return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
}

方法重载:

/**
 * 签名
 *
 * @param content    待签名字符串(明文)
 * @param privateKey 密钥
 * @return 签名字符串(密文)
 * @throws Exception
 */
public String encodeSHA1(String content, String privateKey) throws Exception {
    PrivateKey key = this.getPrivateKey(privateKey);
    return encodeSHA1(content, key);
}

/**
 * 验签
 *
 * @param content   待验签字符串(密文)
 * @param sign      待比较的签名
 * @param publicKey 公钥
 * @return 验签结果
 * @throws Exception
 */
public boolean decodeSHA1(String content, String sign, String publicKey) throws Exception {
    PublicKey key = this.getPublicKey(publicKey);
    return decodeSHA1(content, sign, key);
}

调用过程:

public static void main(String[] args) throws Exception {
    RSAUtil util = new RSAUtil();

    // 生成密钥对
    KeyPair keyPair = util.genKeyPair();

    // 签名与验签(SHA1)
    String content2 = "lewis2951";
    String sign = util.encodeSHA1(content2, privateKey);
    boolean verify = util.decodeSHA1(content2, sign, publicKey);
    log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
}

三、附录

1、完整实现

package org.lewis.demo.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

@Slf4j
public class RSAUtil {

    /**
     * 生成密钥对
     *
     * @return
     * @throws Exception
     */
    public KeyPair genKeyPair() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(512);
        return keyGen.generateKeyPair();
    }

    /**
     * 加密
     *
     * @param content   原始字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception
     */
    public String encrypt(String content, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return new String(Base64Utils.encode(bytes));
    }

    /**
     * 解密
     *
     * @param content    密文字符串
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception
     */
    public String decrypt(String content, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] bytes = cipher.doFinal(Base64Utils.decodeFromString(content));
        return new String(bytes);
    }

    private PublicKey getPublicKey(String publicKey) throws Exception {
        byte[] keyBytes = Base64Utils.decode(publicKey.getBytes(StandardCharsets.UTF_8));

        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");

        return kf.generatePublic(keySpec);
    }

    private PrivateKey getPrivateKey(String privateKey) throws Exception {
        byte[] keyBytes = Base64Utils.decode(privateKey.getBytes(StandardCharsets.UTF_8));

        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");

        return kf.generatePrivate(keySpec);
    }

    /**
     * 加密
     *
     * @param content   原始字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception
     */
    public String encrypt(String content, String publicKey) throws Exception {
        PublicKey key = this.getPublicKey(publicKey);
        return encrypt(content, key);
    }

    /**
     * 解密
     *
     * @param content    密文字符串
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception
     */
    public String decrypt(String content, String privateKey) throws Exception {
        PrivateKey key = this.getPrivateKey(privateKey);
        return decrypt(content, key);
    }

    /**
     * 签名
     *
     * @param content    待签名字符串(明文)
     * @param privateKey 密钥
     * @return 签名字符串(密文)
     * @throws Exception
     */
    public String encodeMD5(String content, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return new String(Base64Utils.encode(signature.sign()));
    }

    /**
     * 验签
     *
     * @param content   待验签字符串(密文)
     * @param sign      待比较的签名
     * @param publicKey 公钥
     * @return 验签结果
     * @throws Exception
     */
    public boolean decodeMD5(String content, String sign, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(publicKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 签名
     *
     * @param content    待签名字符串(明文)
     * @param privateKey 密钥
     * @return 签名字符串(密文)
     * @throws Exception
     */
    public String encodeMD5(String content, String privateKey) throws Exception {
        PrivateKey key = this.getPrivateKey(privateKey);
        return encodeMD5(content, key);
    }

    /**
     * 验签
     *
     * @param content   待验签字符串(密文)
     * @param sign      待比较的签名
     * @param publicKey 公钥
     * @return 验签结果
     * @throws Exception
     */
    public boolean decodeMD5(String content, String sign, String publicKey) throws Exception {
        PublicKey key = this.getPublicKey(publicKey);
        return decodeMD5(content, sign, key);
    }

    /**
     * 签名
     *
     * @param content    待签名字符串(明文)
     * @param privateKey 密钥
     * @return 签名字符串(密文)
     * @throws Exception
     */
    public String encodeSHA1(String content, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA1withRSA");
        signature.initSign(privateKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return new String(Base64Utils.encode(signature.sign()));
    }

    /**
     * 验签
     *
     * @param content   待验签字符串(密文)
     * @param sign      待比较的签名
     * @param publicKey 公钥
     * @return 验签结果
     * @throws Exception
     */
    public boolean decodeSHA1(String content, String sign, PublicKey publicKey) throws Exception {
        Signature signature = Signature.getInstance("SHA1withRSA");
        signature.initVerify(publicKey);
        signature.update(content.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64Utils.decode(sign.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 签名
     *
     * @param content    待签名字符串(明文)
     * @param privateKey 密钥
     * @return 签名字符串(密文)
     * @throws Exception
     */
    public String encodeSHA1(String content, String privateKey) throws Exception {
        PrivateKey key = this.getPrivateKey(privateKey);
        return encodeSHA1(content, key);
    }

    /**
     * 验签
     *
     * @param content   待验签字符串(密文)
     * @param sign      待比较的签名
     * @param publicKey 公钥
     * @return 验签结果
     * @throws Exception
     */
    public boolean decodeSHA1(String content, String sign, String publicKey) throws Exception {
        PublicKey key = this.getPublicKey(publicKey);
        return decodeSHA1(content, sign, key);
    }

    public static void main(String[] args) throws Exception {
        RSAUtil util = new RSAUtil();

        // 生成密钥对
        KeyPair keyPair = util.genKeyPair();

        // 打印密钥对字符串
        String publicKey = new String(Base64Utils.encode(keyPair.getPublic().getEncoded()));
        String privateKey = new String(Base64Utils.encode(keyPair.getPrivate().getEncoded()));
        log.info("\n公钥: {}\n私钥: {}", publicKey, privateKey);

        // 加密和解密
        String content = "Hello World!";
//        String encryptContent = util.encrypt(content, keyPair.getPublic());
//        String decryptContent = util.decrypt(encryptContent, keyPair.getPrivate());
//        log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);
        String encryptContent = util.encrypt(content, publicKey);
        String decryptContent = util.decrypt(encryptContent, privateKey);
        log.info("\n原始字符串: {}\n加密后:{}\n解密后:{}", content, encryptContent, decryptContent);

        // 签名与验签(MD5)
//        String content2 = "lewis2951";
//        String sign = util.encodeMD5(content2, privateKey);
//        boolean verify = util.decodeMD5(content2, sign, publicKey);
//        log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);

        // 签名与验签(SHA1)
        String content2 = "lewis2951";
        String sign = util.encodeSHA1(content2, privateKey);
        boolean verify = util.decodeSHA1(content2, sign, publicKey);
        log.info("\n原始字符串:{}\n签名字符串:{}\n验签结果:{}", content2, sign, verify);
    }

}