1.简介
加密方法分为对称加密和非对称加密,区别在于对称加密只有有一个密钥,而非对称加密有两个密钥
- 对称加解密:enccrypt(明文,密钥)=密文,decrypt(密文,密钥)=明文
- 非对称加解密:encrypt(明文,公钥)=密文,decrypt(密文,私钥)=明文
2.密钥
2.1.密钥格式
2.1.1.der
.der密钥以二进制编码,在JAVA中需要以流的形式读取密钥。下面是获取公钥的例子:
/**
* der公钥解密,密文Base64输出
*/
public static PublicKey getPublicKeyByDer(InputStream is) throws Exception {
try {
if (certificatefactory == null) {
certificatefactory = CertificateFactory.getInstance("X.509");
}
Certificate cert = certificatefactory.generateCertificate(is);
return cert.getPublicKey();
}finally {
if (is != null) {
is.close();
}
}
}
2.1.2.pem
.pem以"-----BEGIN..."开头, "-----END..."结尾,内容是BASE64编码。下面是获取公钥的例子:
public static PublicKey getPublicKeyByPem(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
return keyFactory.generatePublic(keySpec);
}
2.1.3.PKCS12
.p12PKCS#12证书,简单的说,它包含证书和私钥,并允许口令保护。 下面是获取私钥的例子:
public static Key getP12Key() throws Exception{
try (InputStream is = new FileInputStream( "xx.p12")) {
if (certificatefactory == null) {
certificatefactory = CertificateFactory.getInstance("X.509");
}
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(is, "p12证书的密码".toCharArray());
Key key = keystore.getKey("p12证书的别名", "p12证书的密码".toCharArray());
key.getAlgorithm();//密钥对应的加密算法,可以用在 Cipher.getInstance(key.getAlgorithm())
return key;
}
}
2.1.4.纯字符串
字符串密钥也可以是Base64编码或者16进制编码,甚至直接是字符串,想办法把密钥转换为byte[]就可以生成密钥。
RSA密钥可以这样获取:
public static PublicKey getRSAPublicKey(byte[] publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
return keyFactory.generatePublic(keySpec);
}
AES密钥可以这样获取:
public static Key getKey(byte[] key) throws Exception {
return new SecretKeySpec(key, "AES");
}
2.2.类型
X509EncodedKey
PKCS8EncodedKey
RSAPublicKey
RSAPrivateKey
DSAPublicKey
DSAPrivateKey
2.3.长度
- 128bit
- 256bit JAVA对AES的密钥长度有限制,最多不能超过128bit,也就是16位字符串。如果是256bit的密钥,也就是32位字符串,可以通过修改JRE的文件或者使用CryptoJS的AES方法来加密解密。
2.4.密钥加密
- PBKDF2WithHmacSHA1PBKDF2(Password-Based Key Derivation Function)是一个用来导出密钥的函数,常用于生成加密的密码。它的基本原理是通过一个伪随机函数(例如HMAC函数),把明文和一个盐值作为输入参数,然后重复进行运算,并最终产生密钥。如果重复的次数足够大,破解的成本就会变得很高。而盐值的添加也会增加“彩虹表”攻击的难度。
以下是PBKDF2WithHmacSHA1的JAVA实现
private static final String DEFAULT_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final int DEFAULT_ITERATIONS = 10000;
private static final int DEFAULT_HASH_SIZE = 128;
private static final byte[] DEFAULT_SALT = new byte[]{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
public static byte[] hash(String key) throws Exception {
char[] password = key.toCharArray();
return hash(password, DEFAULT_ALGORITHM, DEFAULT_ITERATIONS, DEFAULT_HASH_SIZE, DEFAULT_SALT);
}
/**
* @param password 密钥
* @param algorithm 运算算法
* @param iteration 进行重复计算的次数
* @param hashSize 期望得到的密钥的长度
* @param salt 盐值
* @return 最后生成的密钥
* @throws Exception 各种异常
*/
public static byte[] hash(char[] password, String algorithm, int iteration, int hashSize, byte[] salt) throws Exception {
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, iteration, hashSize);
return skf.generateSecret(spec).getEncoded();
}
3.加密算法
3.1.对称
3.1.1.DES
DES是对称性加密里面常见一种,全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法。密钥长度是64位(bit),超过位数密钥被忽略。所谓对称性加密,加密和解密密钥相同。对称性加密一般会按照固定长度,把待加密字符串分成块。不足一整块或者刚好最后有特殊填充字符。往往跨语言做DES加密解密,经常会出现问题。往往是填充方式不对、或者编码不一致、或者选择加密解密模式(ECB,CBC,CTR,OFB,CFB,NCFB,NOFB)没有对应上造成。常见的填充模式有: 'pkcs5','pkcs7','iso10126','ansix923','zero' 类型,包括DES-ECB,DES-CBC,DES-CTR,DES-OFB,DES-CFB
public static byte[] desEncrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("DES");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
3.1.2.3DES
3DES(又叫Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。它相当于是对每个数据块应用三次DES加密算法。密钥长度是128位,192位(bit),如果密码位数少于等于64位,加密结果与DES相同。原版DES容易被破解,新的3DES出现,增加了加密安全性,避免被暴力破解。它同样是对称性加密,同样涉及到加密编码方式,及填充方式。包括3DES-ECB,3DES-CBC,3DES-CTR,3DES-OFB,3DES-CFB
public static byte[] desedeEncrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("DESede");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DESede");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
3.1.3. AES
AES,高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128 比特,密钥长度则可以是128,192或256比特;而Rijndael使用的密钥和区块长度可以是32位的整数倍,以128位为下限,256比特为上限。包括AES-ECB,AES-CBC,AES-CTR,AES-OFB,AES-CFB
public static byte[] aesEncrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
AES的算法模式和填充有如下几种选择,其中CBC需要偏移量而ECB可以不用偏移量。NoPadding为不填充,这时候需要手动保证加密或者解密的内容的字节数是16的倍数;PKCS5Padding和PKCS7Padding是几乎一样的,可以通用,填充的内容是填充字节序列的长度,例如如果byte数组的长度为15,需要填充1位才达到16,那就填充0x1,如果byte数组长度为1,则需要填充15个0xf;ZeroPadding是用0x0来填充。
Cipher.getInstance("AES/CBC/NoPadding");
Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher.getInstance("AES/CBC/PKCS7Padding");
Cipher.getInstance("AES/CBC/ZeroBytePadding");
Cipher.getInstance("AES/ECB/NoPadding");
Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher.getInstance("AES/ECB/PKCS7Padding");
Cipher.getInstance("AES/ECB/ZeroBytePadding");
另外,AES的密钥长度分别是128byte、192byte、256byte,对应的字符串长度分别是16位、24位、32位。当密钥大于128byte,也就是16位字符串长度时,代码会抛出java.security.InvalidKeyException: Illegal key size or default parameters。Illegal key size or default parameters是指密钥长度是受限制的,java运行时环境读到的是受限的policy文件。文件位于${java_home}/jre/lib/security,这种限制是因为美国对软件出口的控制。解决方法有两个
- 替换${java_home}/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar。
- 使用CryptoJs,这是谷歌开源的用Js实现的AES加解密库,速度和原生JDK相差无几,除了第一次载入Js文件需要额外耗时。
3.1.4.RC4
RC4加密算法是RSA三人组中的头号人物Ron Rivest在1987年设计的密钥长度可变的流加密算法簇。该算法的速度可以达到DES加密的10倍左右,且具有很高级别的非线性。1994年9月,它的算法被发布在互联网上。由于RC4算法加密是采用的xor,所以,一旦子密钥序列出现了重复,密文就有可能被破解。RC4作为一种老旧的验证和加密算法易于受到黑客攻击,现在逐渐不推荐使用了。
public static byte[] rc4Encrypt(byte[] data, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("RC4");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "ARCFOUR");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
3.2.非对称
3.2.1. RSA
加解密密钥不一致,一般私钥不公开,使用公钥加密,私钥解密,使用私钥加密,公钥可以解密。与AES不同,每次加密出来的结果是不一样的。而且需要执行分组加密,RSA加密每一组最多117个byte的长度,解密每一组最多128个byte的长度。
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
PublicKey key = keyFactory.generatePublic(keySpec);
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行分组加密操作,RSA加密每一组最多117位长度
int segment = 117;
int inputLen = data.length;
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > segment) {
cache = cipher.doFinal(data, offSet, segment);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * segment;
}
return out.toByteArray();
}
}
public static byte[] encryptByPrivateKey(byte[] data, byte[] publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
PrivateKey key = keyFactory.generatePrivate(keySpec);
cipher.init(Cipher.ENCRYPT_MODE, key);
//执行分组加密操作,RSA加密每一组最多117位长度
int segment = 117;
int inputLen = data.length;
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > segment) {
cache = cipher.doFinal(data, offSet, segment);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * segment;
}
return out.toByteArray();
}
}
3.3.补充额外算法
添加BouncyCastle的依赖,在程序初始化时调用一次
Security.addProvider(new BouncyCastleProvider());
就可在Cipher.getInstance("")方法中获取更多加密算法。
4.签名
4.1.算法
4.1.1.SHA1withRSA
/**
* RSA签名
*
* @param content 待签名数据
* @param privateKey 商户私钥
* @param encode 字符集编码
* @return 使用Base64编码的签名值
*/
public static String sign(String content, String privateKey, String encode) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, SignatureException {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
java.security.Signature signature = java.security.Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(content.getBytes());
byte[] signed = signature.sign();
return Base64.encode(signed);
}
4.1.2. 其余通用私钥签名
/**
* 私钥签名底层操作
*
* @param algorithm NONEwithRSA
* MD2withRSA,MD5withRSA
* SHA1withRSA,SHA256withRSA ,SHA384withRSA ,SHA512withRSA
* NONEwithDSA
* SHA1withDSA
* NONEwithECDSA ,SHA1withECDSA ,SHA256withECDSA ,SHA384withECDSA ,SHA512withECDSA
* @param data byte[]
* @param privateKey PrivateKey
* @return byte[]
*/
public static byte[] sign(String algorithm, byte[] data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
5.散列
- SHA1
- SHA256
- SHA384
- SHA512
- MD2
- MD5
apache-commons-codec的DigestUtils有全部实现。
参考资料
https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html Java官方实现的各种加密算法名称的文档
https://www.example-code.com/java/rsa.asp RSA例子
https://www.programcreek.com/java-api-examples/ 查找各种算法和密钥的demo
http://tool.chacuo.net/cryptaes 校验加密算法正确性的工具网站