和第三方系统对接时,需要对隐私数据进行加密,对请求报文进行签名等。加密算法分为单向加密、对称加密、非对称加密等,其对应的算法也各式各样。Java 提供了统一的框架(java.security.*)来规范安全加密。下面将一一介绍以下内容。
- 加密算法概念及分类
- 密钥生成
- 摘要算法工具 - MessageDigest
- 签名算法工具 - Signature
- 常用加密工具类 - Cipher
- Certificate - 证书的保存
- KeyStore - 密钥证书的实体类
- Https 证书加载
一、加密算法概念及分类
常用的加密算法类型有三种,如下:
- 单向加密
单向加密又称为不可逆加密,在加密过程中不使用密钥,明文由系统加密处理成密文,密文无法解密,一般适合于验证。在验证过程中,重新输入明文,并经过同样的加密算法处理,得到相同的密文并被系统重新认证。算法有 MD5、SHA1、HMAC等。 - 对称加密
也就是加密方和解密方利用同一个密钥对数据进行加密和解密,例如 DES、PBE 等等。 - 非对称加密
非对称加密算法用到的密钥分为公钥和私钥,二者是非对称的。例如用公钥加密的内容需要使用私钥来解密,使用私钥加密的内容需要用公钥来解密。非对称加密算法有 DSA、RSA等。
二、密钥生成
2.1 对称密钥生成
- KeyGenerator(javax.crypto 包下)用于生成对称密钥。
- 支持算法:AES、ARCFOUR、DES、DESede、HmacMD5、HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512、RC2 等。
KeyGenerator 中的几个重要的方法如下。
public static final KeyGenerator getInstance(String algorithm, String provider)
public static final KeyGenerator getInstance(String algorithm)
public final void init(int keysize)
public final void init(int keysize, SecureRandom random)
public final void init(SecureRandom random)
public final void init(AlgorithmParameterSpec params, SecureRandom random)
public final SecretKey generateKey()
示例:
public static void main(String[] args) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("DES");
SecureRandom random = new SecureRandom();
random.nextBytes(new byte[128]);
kg.init(56, random);
SecretKey sk = kg.generateKey();
byte[] b = sk.getEncoded();
System.out.println(new String(Base64.encodeBase64(b))); // XZdrC/in5uk=
}
2.2 非对称密钥生成
- KeyPairGenerator(java.security 包下) 用于生成非对称加密算法的密钥对 KeyPair(java.security 包下),KeyPair 会包括一个公钥和私钥。
- 支持算法:DiffieHellman、DSA、RSA、RSASSA-PSS、EC 等。
KeyPairGenerator.java 和 KeyPair.java 类中的重要的方法如下所示。
// KeyPairGenerator.java
public static KeyPairGenerator getInstance(String algorithm)
public static KeyPairGenerator getInstance(String algorithm, String provider)
public void initialize(int keysize, SecureRandom random)
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
public final KeyPair genKeyPair()
// KeyPair.java
public PublicKey getPublic()
public PrivateKey getPrivate()
示例:
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
random.nextBytes(new byte[516]);
keyGen.initialize(516, random);
KeyPair keyPair = keyGen.genKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
byte[] publicKeyEncoded = publicKey.getEncoded();
byte[] privateKeyEncoded = privateKey.getEncoded();
System.out.println(new String(Base64.encodeBase64(publicKeyEncoded)));
// MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBCJQ3Ee/oxid0CkYxQaNyUlPlIJKFUuwB+kYAuZ5OdxJjSRHJ7jb931aIU+t61DhG2BBiegs3588SyGRe8IQZM10CAwEAAQ==
System.out.println(new String(Base64.encodeBase64(privateKeyEncoded)));
// MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEIlDcR7+jGJ3QKRjFBo3JSU+UgkoVS7AH6RgC5nk53EmNJEcnuNv3fVohT63rUOEbYEGJ6CzfnzxLIZF7whBkzXQIDAQABAkEFjPhQp7whMXe4ChBmmr0mHVf7ijGvJDpnVxGzB4VXL0+5TGT0fptb85dNjVmKD2REe0fBntRh7hSZETgYCiZMgQIhAwPbBuZ3QjDhMfx3fb89xCnLZFzEILzvKXeS1Q5xx/ehAiEC2Go0R13hndaDIzhq/Rn2fsVLzAlAFXIJBEdTVZ498j0CIQKYbA/JjjmVWBUubjH50RKuo54WWOKRoRLCEsyCraFkYQIgS4MnDEb1PrGgQqR0ouxwG1BEvVAwLoj12lWyk+ulrFkCIQJyg5t1WkvLVeqrGOGhj5jIkWEfxad/43X2fkFM6WyL3Q==
}
2.3 密钥 Key 和密钥规格 KeySpec 的相互转化
KeySpec 是一个接口,用来组成密钥内容的规范。如果密钥存储在硬件设备上,则其规范可以包含有助于标识该设备上的密钥的信息。
KeySpec 具有规范性,所以一般会根据外部参数生成 KeySpec,再根据 KeySpec 生成对应的 Key。SecretKeyFactory、KeyFactory 的作用就是转换 Key 与 KeySpec。
SecretKeyFactory 用于对称加密的密钥和密钥规格之间的转换,配合 KeyGenerator 使用,支持算法:AES、ARCFOUR、DES、DESede、PBEWithMD5AndDES、PBEWithHmacSHA256AndAES_128、PBKDF2WithHmacSHA256 等。
SecretKeyFactory.java 中的几个方法如下所示。
public static final SecretKeyFactory getInstance(String algorithm)
public static final SecretKeyFactory getInstance(String algorithm, String provider)
public final SecretKey translateKey(SecretKey key)
public final SecretKey generateSecret(KeySpec keySpec)
public final KeySpec getKeySpec(SecretKey key, Class<?> keySpec)
代码示例:
public static void main(String[] args) throws Exception {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
byte[] desKey = "hello world".getBytes(StandardCharsets.UTF_8); // 设置密钥
DESKeySpec keySpec = new DESKeySpec(desKey); // 设置密钥参数
SecretKey key = keyFactory.generateSecret(keySpec); // 得到密钥对象
byte[] b = key.getEncoded();
System.out.println(new String(Base64.encodeBase64(b))); // aGRtbW5XV1c=
}
KeyFactory 用于非对称加密的密钥和密钥规格之间的转换,配合 KeyPairGenerator 使用,支持算法:DiffieHellman、DSA、RSA、RSASSA-PSS、EC 等。
KeyFactory.java 中的几个方法如下所示。
// KeyFactory.java
public static KeyFactory getInstance(String algorithm)
public static KeyFactory getInstance(String algorithm, String provider)
public final PublicKey generatePublic(KeySpec keySpec)
public final PrivateKey generatePrivate(KeySpec keySpec)
public final <T extends KeySpec> T getKeySpec(Key key, Class<T> keySpec)
代码示例:
public static void main(String[] args) throws Exception {
// 生成 RSA 密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
random.nextBytes(new byte[2048]);
keyGen.initialize(2048, random);
KeyPair keyPair = keyGen.genKeyPair();
// PublicKey 转 KeySpec; KeySpec 再转 PublicKey
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyPair.getPublic().getEncoded());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
byte[] pubKeyEncoded = pubKey.getEncoded();
System.out.println(new String(Base64.encodeBase64(pubKeyEncoded)));
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAglncEo1/cWWMQzPxShtZY+zMERNcI6EHgEtYcPJaxF0mLVKk0af/5YB1WMHkEiq/CaRJa1GPS24iATIZpl3ICSqo8gSbgctogOxCU+XBtWhyVBgnaPpt4xoNFpoGyNyZzOvEq4YiNVDMhnTD+0Qlx7jyvV8R3xWcGWyZHB68KGwj9NvxHcsxkDZzXqAkDdiqdmqlImQoS9NBdRWQarX/rcBooQ9qs3yxu8i/bufVwwQS+lPvZoaKzJOvA4/qraa+ffXPJjsyLZk67C6ckQjmRbhgGuoa6fJ7FCRc98qZy9Cx/NxiqypHkIG0glgoOA4u1RIHBOEWY4Vo3G0d0Tw5PwIDAQAB
// PrivateKey 转 KeySpec; KeySpec 再转 PrivateKey
PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(keyPair.getPrivate().getEncoded());
PrivateKey priKey = keyFactory.generatePrivate(priKeySpec);
byte[] priKeyEncoded = priKey.getEncoded();
System.out.println(new String(Base64.encodeBase64(priKeyEncoded)));
// MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQCCWdwSjX9xZYxDM/FKG1lj7MwRE1wjoQeAS1hw8lrEXSYtUqTRp//lgHVYweQSKr8JpElrUY9LbiIBMhmmXcgJKqjyBJuBy2iA7EJT5cG1aHJUGCdo+m3jGg0WmgbI3JnM68SrhiI1UMyGdMP7RCXHuPK9XxHfFZwZbJkcHrwobCP02/EdyzGQNnNeoCQN2Kp2aqUiZChL00F1FZBqtf+twGihD2qzfLG7yL9u59XDBBL6U+9mhorMk68Dj+qtpr599c8mOzItmTrsLpyRCOZFuGAa6hrp8nsUJFz3ypnL0LH83GKrKkeQgbSCWCg4Di7VEgcE4RZjhWjcbR3RPDk/AgMBAAECggEBAIB/TsfnPvOtNEjnQnxYW5V60Gwg1pq02i0pmUS2VK3wWXsiViHraAJ40LUvZcJW6z34+vtVSloEdncRSWHMXy5SJHt3+UhJGXrF7FjCTGOlU9b8fJUrEfpnKvHV4sxNUzxESvr/XmeKgCQnpS7kLg4ljv0JZBezOM+DU6f50GhTSWJeTGzCOfGl6xXjpLeeYnnbEtCKG5qo3YH3wAeFF0Zvb6Rqm0pXJwQ/YMUNFG/2CoAktKtUfpF6DSbmFzzRA03nWqRrGaWVqtzCmQ7ndSWsdeG00RjNRkA94VevoHZV9G3rLgzPmfWF5nn1QT/SGAUAU+VmgoG0dcmDyivN91ECgYEA48zUx3Mw09NCl9TAofHFFAY6TdR/7dmThzTsEzJoCzyjvLOEUExMI8w6W2t5d4jYx3hxgQoK8mbZfn9vWrK9IEZEsQ25ec/6kqW+hpcwXTaN5aTWRUILJTz5yHothaEbK5sDSRTRFvEVlxszikmJfT6Z1/4rQr0mw+a4ZuOp61kCgYEAknzJunQYfLOvSnJocX9ms5sFkA+wFq9twHXptUTeh2wcxz0C2zRobsrlrW8CwWG5q9P90ywRXCaoM3fp5w+x8qPLmdzW/GNQC/F5o+d/SnNYMSdPr4OS14I3gu8zMO9eTWnLY8IYoxeXRl0/5/ewtM8WvsW/KrXErYjqf9elblcCfwHHl+H3BGqjO+Hzx418Vg3R/qKdBmLVUFG+GBoOSsHLt3vB60a1UeL1tX8BV/GXIBpu1nQrn+pE424ZkMUkoFWgNukrMkfBWDPNF6/1fms8Ad/JaeMgoPWphEoMqk5g89VjYKMxhnCncYO8sqph6LERzCHj2nKrB6KAKvCi1rECgYABCqYcj0rFSDnM27dmZzOBv25wscvcvW6YWb5Jra2vZNNnj0V/7YV4lDTB4PIyEdHSKPW7FKsi7ptvkkC1heUMBqIh+/IDZWliTFtDERhUnTFZWCA27UaUBbcDVVQV2v3eqwvpL64hKr/Gnk8gBSDaiEZvINTVJum5GiogspXYjQKBgAV2DXc2P4AHqt981ou3GPtFsAfcL3JjwslnIkgNk7nEReSTmsJ2rERrLUKQEIKLNdxBOrK/0AgsK8+ysFk/+lmVd9rGY3kROF4HRvV8aIoLGkKlpIjhNhTpmlWzfFrMXebtiK2iEfbZcC1wedl+V00Q9YntOYMlJPdfhl1wxN7H
}
三、摘要算法 - MessageDigest 和 javax.crypto.Mac(HMAC)
- 单向加密是不可逆的,MD5、SHA、MAC 都是单向加密算法,也称之为摘要算法。
- MD5、SHA 会根据明文用哈希算法计算一个固定长度的摘要(哈希值),然后把明文和摘要发送给接收者,接收者根据同样的算法计算出摘要,对比两个摘要是否一样即可验证明文的正确性。它的应用场景是防止报文被篡改和校验数据。
- MD5、SHA 等算法是开源的,容易被试探出来。有没有更安全的摘要算法呢?HMAC(带密钥的哈希函数),用一个密钥和一个明文消息作为输入,生成一个消息摘要。密钥一般使用KeyGenerator 创建,相当于一个密码值,其被试探出的概率小。
- MessageDigest 支持的算法:MD2、MD5、SHA-1、SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。
- javax.crypto.Mac支持的算法:HmacMD5、HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512、PBEWithHmacSHA1。
- MD5 的示例如下所示。
public static void main(String[] args) throws Exception {
MessageDigest digest = MessageDigest.getInstance("MD5");
System.out.println(new String(Base64.encodeBase64(digest.digest("hello world!".getBytes()))));
// /D/5joxqDTCH1RXARz+Gdw==
System.out.println(new String(Base64.encodeBase64(digest.digest("hello happy!".getBytes()))));
// mfzFDfIxtbUOJRN7WGSNVA==
}
- MAC 的示例如下所示。
public static void main(String[] args) throws Exception {
// 初始化 HmacMD5 摘要算法的密钥产生器
KeyGenerator generator = KeyGenerator.getInstance("HmacMD5");
// 产生密钥
SecretKey secretKey = generator.generateKey();
// SecretKeySpec 继承于 SecretKey 和 KeySpec,因此可直接用 SecretKeySpec 初始化 Mac
// SecretKey secretKey = new SecretKeySpec("password".getBytes(), "HmacMD5");
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secretKey);
// 计算摘要
String data = "hello world";
byte[] result1 = mac.doFinal(data.getBytes());
System.out.println(new String(Base64.encodeBase64(result1)));
}
四、签名算法工具 - Signature
签名后的数据具有唯一标识性,就像一个人的签名能代表一个人的身份。签名一般是指用非对称加密算法的私钥来加密明文的过程,生成的密文可以被持有公钥的人识别解密,只要你的公钥是准确无误的,就能保证你解密的数据是来自持有私钥的一方。
如何保证公钥是正确无误,没被篡改的?
- 一对一给你;
- 获取公钥后通过权威机构认证。
支持算法:NONEwithRSA、MD2withRSA、MD5withRSA、SHA512/224withRSA、SHA512/256withRSA、RSASSA-PSS、NONEwithDSA、SHA512withDSA、NONEwithECDSA、SHA512withECDSA、MD5withRSAandMGF1(太多了,此处选择性列举几个)
Signature 配合 KeyPairGenerator 使用,进行数据签名和验签的 demo 如下所示。
public static void main(String[] args) throws Exception {
// 生成 RSA 密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
random.nextBytes(new byte[1024]);
keyGen.initialize(1024, random);
KeyPair keyPair = keyGen.genKeyPair();
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(keyPair.getPrivate());
// 待签名字符串
String content = "hello world";
byte[] data = content.getBytes("UTF-8");
// 数据签名
signature.update(data);
byte[] digest = signature.sign();
Base64.Encoder encoder = Base64.getEncoder();
System.out.println("签名后的字符串:" + encoder.encodeToString(digest));
// 数据验签
signature.initVerify(keyPair.getPublic());
signature.update(data);
System.out.println("验签结果:" + signature.verify(digest));
}
五、常用加密工具类 - Cipher
用于加密/解密数据。支持各种类型的算法:对称加密(例如 AES),非对称加密(例如RSA)。
支持算法:AES、AESWrap、ARCFOUR、Blowfish、DES、DESede、DESedeWrap、ECIES、RSA(太多了,选择性列举几个)。
示例见:RSA 加解密(Java 实现)
六、Certificate - 证书存储
- CertificateFactory:用于创建公钥证书(Certificate)和证书吊销列表(CRL)
- Certificate 及其子类 X509Certificate
- CertPath 和 CertPathBuilder:用于构建证书链(也称为证书路径)
- CertPathValidator:用于验证证书链
- CRL:证书吊销列表
- CertStore:用于存储检索证书和 CRL
示例如下。
import java.io.InputStream;
import java.security.cert.CertificateFactory;
// certificateStream 是证书的输入流
public static PublicKey getPublicKeyByCer(InputStream certificateStream) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
Certificate certificate = (Certificate) certificateFactory.generateCertificate(certificateStream);
return certificate.getPublicKey();
}
七、KeyStore - 密钥证书的实体类
- KeyStore 用于存储私钥和证书(公钥在证书 Certificate 里面)
- 公钥:是一个详细的实体的数字关联,并有意让所有想同这个实体发生信任关系的其他实体知道。公钥用来检验签名。
- 私钥:是一些数字,私钥和公钥是一对密钥对,公钥用来加密数据,私钥用来签名。公钥加密的消息只能用私钥解密,私钥签名的消息只能用公钥检验签名。
八、java.https 加载证书的 API
KeyManagerFactory、TrustManagerFactory => KeyManager、TrustManager => SSLContext => SSLEngine、SSLSocketFactory、SSLSocket
一般的证书加载过程:
- 用 Certificate、KeyStore 生成创建 KeyManagerFactory 和 TrustManagerFactory
- KeyManagerFactory 和 TrustManagerFactory 用来创建 KeyManager 和 TrustManager
- KeyManager 和 TrustManager 用来初始化 SSLContext
- 然后使用 SSLContext,创建实际实现 SSL/TLS 协议的对象(SSLEngine、SSLSocketFactory 或者 SSLSocket)
- SSLSocket 和 SSLEngine 可以直接在通信对象中使用
- KeyManager 和 TrustManager 作用:
- KeyManager 负责向对等端显示使用的凭证(使用的密码标准、加密算法、证书、公钥、签名等)
- TrustManager 负责验证从对等端收到的凭证,验证凭证有多种方式,其中之一是创建 CertPath 对象,并让 JDK 的内置公钥基础结构(PKI)框架处理验证。 在内部,CertPath 实现可能会创建一个 Signature 对象,并使用它来验证证书链中的每个签名。