京东科技 姚永健
一、术语表:
1. 对称算法
加密解密密钥是雷同的。这些算法也叫机密密钥算法或单密钥算法,它要求发送者和接收者在平安通信之前,约定一个密钥。对称算法的安全性依赖于密钥,透露密钥就意味着任何人都能对音讯进行加密解密。只有通信须要窃密,密钥就必须窃密。
对称算法可分为两类。一次只对明文中的单个位(有时对字节)运算的算法称为序列算法或序列密码。另一类算法是对明文的一组位进行运算,这些位组称为分组,相应的算法称为分组算法或分组明码。古代计算机明码算法的典型分组长度为 64 位――这个长度大到足以避免剖析破译,但又小到足以不便作用。
2. 非对称算法
非对称算法也叫公开密钥加密, 它是用两个数学相干的密钥对信息进行编码。在此零碎中,其中一个密钥叫公开密钥,可随便发给冀望与密钥持有者进行平安通信的人。公开密钥用于对信息加密。第二个密钥是公有密钥,属于密钥持有者,此人要认真保留公有密钥。密钥持有者用公有密钥对收到的信息进行解密。
一般来说,都是公钥加密,私钥解密。如果零碎单方须要互相通信,能够生成两对密钥对。各自保留好本人的私钥和对方的公钥,用公钥加密,私钥进行解密
3. 可逆加密算法
一般来说,波及到秘钥之类的算法,都是可逆的。意思就是通过算法和秘钥加密之后,能够再次通过解密算法还原。常见的有 DES、3DES、AES128、AES192、AES256。其中 AES 前面的数字代表的是密钥长度。对称加密算法的安全性绝对较低,比拟实用的场景就是内网环境中的加解密。
4. 不可逆算法
常见的不可逆加密算法有 MD5,HMAC,SHA1、SHA-224、SHA-256、SHA-384,和 SHA-512,其中 SHA-224、SHA-256、SHA-384,和 SHA-512 咱们能够统称为 SHA2 加密算法,SHA 加密算法的安全性要比 MD5 更高,而 SHA2 加密算法比 SHA1 的要高。其中 SHA 前面的数字示意的是加密后的字符串长度,SHA1 默认会产生一个 160 位的信息摘要。
不可逆加密算法最大的特点就是不须要密钥
5. 加密盐
加密盐也是比拟常听到的一个概念,盐就是一个随机字符串用来和咱们的加密串拼接后进行加密。加盐次要是为了保障加密字符串的安全性。如果有一个加盐后的加密串,黑客通过肯定伎俩失去这个加密串,他解密后拿到的明文,并不是咱们加密前的字符串,而是加密前的字符串和盐组合的字符串,这样相对来说又减少了字符串的安全性
或者也能够用在签名,例如签名是对明文或者密文加盐后的签名,有人想串改数据,如果不晓得这个盐和规定,那么接管方验签就会不通过,从而保障通信的平安。
二、传统加密算法介绍
DES(Data Encryption Standard):
对称算法,数据加密规范,速度较快,实用于加密大量数据的场合。
AES 算法:
是 DES 的升级版,属于对称算法。可逆
代码:AESUtil
package AES;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AESUtil {
/**
* AES 加密字符串
*
* @param content
* 须要被加密的字符串
* @param password
* 加密须要的明码
* @return 密文
*/
public static byte[] encrypt(String content, String password) {
try {KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创立 AES 的 Key 生产者
kgen.init(128, new SecureRandom(password.getBytes()));// 利用用户明码作为随机数初始化出
// 加密没关系,SecureRandom 是生成平安随机数序列,password.getBytes()是种子,只有种子雷同,序列就一样,所以解密只有有 password 就行
SecretKey secretKey = kgen.generateKey();// 依据用户明码,生成一个密钥
byte[] enCodeFormat = secretKey.getEncoded();// 返回根本编码格局的密钥,如果此密钥不反对编码,则返回
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 转换为 AES 专用密钥
Cipher cipher = Cipher.getInstance("AES");// 创立明码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化为加密模式的明码器
byte[] result = cipher.doFinal(byteContent);// 加密
return result;
} catch (NoSuchPaddingException e) {e.printStackTrace();
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
} catch (UnsupportedEncodingException e) {e.printStackTrace();
} catch (InvalidKeyException e) {e.printStackTrace();
} catch (IllegalBlockSizeException e) {e.printStackTrace();
} catch (BadPaddingException e) {e.printStackTrace();
}
return null;
}
/**
* 解密 AES 加密过的字符串
*
* @param content
* AES 加密过过的内容
* @param password
* 加密时的明码
* @return 明文
*/
public static byte[] decrypt(byte[] content, String password) {
try {KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创立 AES 的 Key 生产者
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();// 依据用户明码,生成一个密钥
byte[] enCodeFormat = secretKey.getEncoded();// 返回根本编码格局的密钥
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");// 转换为 AES 专用密钥
Cipher cipher = Cipher.getInstance("AES");// 创立明码器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化为解密模式的明码器
byte[] result = cipher.doFinal(content);
return result; // 明文
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
} catch (NoSuchPaddingException e) {e.printStackTrace();
} catch (InvalidKeyException e) {e.printStackTrace();
} catch (IllegalBlockSizeException e) {e.printStackTrace();
} catch (BadPaddingException e) {e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {String a = Base64.getEncoder().encodeToString("435678(*&*&^*&^%&^$%^#%#!@#$%^&%%90|8752$".getBytes(StandardCharsets.UTF_8));
String b = new String(Base64.getDecoder().decode(a.getBytes(StandardCharsets.UTF_8)));
System.out.println(a+":"+b);
String content = "{'aaa':'111','bbb':'222'}";
String secretKey = "43567890|8752$";
System.out.println("须要加密的内容:" + content);
byte[] encrypt = encrypt(content, secretKey);
System.out.println("加密后的 2 进制密文:");
System.out.println(new String(encrypt));
String hexStr = Base64.getEncoder().encodeToString(encrypt);
System.out.println("base64 编码后密文:" + hexStr);
byte[] byte2 = Base64.getDecoder().decode(hexStr);
System.out.println("解码后转换为 2 进制后密文:");
System.out.println(new String(byte2));
byte[] decrypt = decrypt(byte2, secretKey);
System.out.println("解密后的内容:" + new String(decrypt,"utf-8"));
}
}
RSA 算法:
公钥加密算法,非对称,可逆
代码:RSAUtil
package RSASha256;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.*;
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 java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
private static final int DEFAULT_RSA_KEY_SIZE = 2048;
private static final String KEY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
public static void main(String [] args){Map<String,String> result = generateRsaKey(DEFAULT_RSA_KEY_SIZE);
String publicKey = result.get("publicKey");
String privateKey = result.get("privateKey");
// String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuazymB25s/ueIfwMM2H74736dQQE4bvRDgNfWltM4PzduNC84ntav41qEgbZtby47O57m/YDVf6vMXJH25ejlsBBhls7FD7xHTkOrskZqLekH0xCs2xa/akdwllbewUNzvWMuQy8X3j2VnrILXzVjvqYxkFY0itMo8fq1xSO6B/S5/RaeKpTtIepFB2cIU9vMrtLoCsnjVoTuPdWcoC79LtS0g1Q5BV4dn+Uca+8/gUbhS7vbth3oLZt6gXTRjJrPxhT4lqWsCZqwkQHPG/8JQgnFqs7+R0KcyemG/411IaZI9WYRGTsQji8sQ2q7HAlZRXvJn+As7jJPvyi67JGKwIDAQAB";
// String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5rPKYHbmz+54h/AwzYfvjvfp1BAThu9EOA19aW0zg/N240Lzie1q/jWoSBtm1vLjs7nub9gNV/q8xckfbl6OWwEGGWzsUPvEdOQ6uyRmot6QfTEKzbFr9qR3CWVt7BQ3O9Yy5DLxfePZWesgtfNWO+pjGQVjSK0yjx+rXFI7oH9Ln9Fp4qlO0h6kUHZwhT28yu0ugKyeNWhO491ZygLv0u1LSDVDkFXh2f5Rxr7z+BRuFLu9u2Hegtm3qBdNGMms/GFPiWpawJmrCRAc8b/wlCCcWqzv5HQpzJ6Yb/jXUhpkj1ZhEZOxCOLyxDarscCVlFe8mf4CzuMk+/KLrskYrAgMBAAECggEABjC+8dVj4J1N+2IU4g2tQT2PQSF+LCx/3tC7+B49JO8pUUUcVwy3zNUhKTKzRXziSXv2ARAlslNIcgSWYrreiGMmjB00jgs/LLM/SxKHWXmt7iEzxBmjuvtNc7JY+3QCrti+9Vh4W1KEHAQB8opL8HVobIu3M2KgLoG20a7syM5gPiCUb3EQ6P30u47y7I2wsbMnMmu79rPu3Q7lSNeoB9KdVV6EA6vukL3chY1QxFh6oFZS1yNjFjxy5RxT8U5Juhr5LYRnOP8MEVptbtrh34SSm0uyckkgJ6jOsrDJqo/DJxjPxixj+BWgG5/XbNq3PFEyEAqkrTZY+FMHSufoaQKBgQDpqPxpl05mDUXCnc7I6F8LZqjnjZU1h9zwKuQe94Fs3I3siWS62sJx1pZ66P9eeIOsT0T4Ye9nDx2x04yyfUcENhydFTQ5M5M9e2q1kKdNeBUT6Xy4WcRibKozUq07mLYSn0kDbuimp/Erp2w3hl5nZu/eLKDjt7yM0otv3uPp5wKBgQDLbYC4hcvk+f1kd/xJQ2ljwF+c8kHHFrlLRipf7qTB24dcrauWXbAGbbcKXx2G+3Waswyf09NdALiWab/jUUUHJm0YGxyr0k5lHci0/eC7hD3tWTL/HRyeF3umKZEkBQypzNeymE6t0lSqjL3MXFQLqumu4h677qA9/DNh7DYhHQKBgQCfAm/bj6s7iba6jVfmozPi91bkVRaAWlgBXL7nT/nU0ncGzC0vd6WxgJ3hQORgLtU0krFV8pfP45qKpHNwGA8XD5gDUiW68500zuM8chdYgeqeJVvJvNUHQfnFeXMIRpFJNPqkCnrqxwk5cvMTCi7+YS/FW0uWDDiVAMcBN4aUawKBgCOgvAiVNk6WEfEEqqTSL6UOzjAYpbiOnEk4src2fpiNMDnlGMYvBmM51/LzEaLQa5p6fV2Ipd4GAE4nmznew+4qprSwGudk3+IJw1sfk7qDwKzPEIVpvddaWYeShB8A22TpwWVAE5eR3M459AvUp8ubVW4RoDxd4Ka6gu1Fh31pAoGAYNPKJNCrdOsQH0nkH0Ld44cH6HD+zcZtoad2eQk0T2SPnKBsIS1S9W0adreOXUv1edO/hN3P/BcTuCaax7ijTscS2f0atc/NIa6LjBnK7oUBBzib9v21L72ZVZ5st4c/H7IzbQCXfS81489a7TTHP+e1HzS/XePftO0pAkr1GJ0=";
System.out.println("公钥为:" + publicKey);
System.out.println("私钥为:" + privateKey);
String plaintext = "{'a':'1111','b':'2222'}";
String ciphertext = null;
try {System.out.println("开始加密明文:"+plaintext);
ciphertext = encrypt(plaintext,publicKey);
} catch (Exception e) {System.out.println("加密失败");
throw new RuntimeException(e);
}
System.out.println("失去的密文为:"+ciphertext);
String deciphering = decrypt(ciphertext,privateKey);
System.out.println("解密后:"+deciphering);
}
/**
* 生成 RSA 公私钥, 可选长度为 1025,2048 位.
*/
public static Map<String,String> generateRsaKey(int keySize) {Map<String,String> result = new HashMap<>(2);
try {KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
// 初始化密钥对生成器,密钥大小为 1024 2048 位
keyPairGen.initialize(keySize, new SecureRandom());
// 生成一个密钥对,保留在 keyPair 中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 失去公钥字符串
result.put("publicKey", new String(Base64.getEncoder().encode(keyPair.getPublic().getEncoded())));
// 失去私钥字符串
result.put("privateKey", new String(Base64.getEncoder().encode(keyPair.getPrivate().getEncoded())));
} catch (GeneralSecurityException e) {e.printStackTrace();
}
return result;
}
/**
* RSA 私钥解密
* @param str 解密字符串
* @param privateKey 私钥
* @return 明文
*/
public static String decrypt(String str, String privateKey) {
//64 位解码加密后的字符串
byte[] inputByte;
String outStr = "";
try {inputByte = Base64.getDecoder().decode(str.getBytes("UTF-8"));
//base64 编码的私钥
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA 解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (UnsupportedEncodingException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException | NoSuchAlgorithmException e) {e.printStackTrace();
}
return outStr;
}
/**
* RSA 公钥加密
* @param str 须要加密的字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异样信息
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64 编码的公钥
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA 加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
public static String sign(String data, String priKey) throws Exception {byte[] decoded = Base64.getDecoder().decode(priKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(new X509EncodedKeySpec(decoded));
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes());
return new String(Base64.getEncoder().encode(signature.sign()));
}
public static boolean verify(String pubKey, String sign, String data) throws Exception{
// 获取 KeyFactory,指定 RSA 算法
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
// 将 BASE64 编码的公钥字符串进行解码
byte[] encodeByte = Base64.getDecoder().decode(pubKey);
// 将 BASE64 解码后的字节数组,结构成 X509EncodedKeySpec 对象,生成公钥对象
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodeByte));
Signature signature = Signature.getInstance("MD5withRSA");
// 加载公钥
signature.initVerify(publicKey);
// 更新原数据
signature.update(data.getBytes("UTF-8"));
// 公钥验签(true- 验签通过;false- 验签失败)return signature.verify(Base64.getDecoder().decode(sign));
}
}
MD5 算法:
信息摘要(Hash 平安散列)算法,也叫哈希算法,哈希值也叫散列值,不可逆,不须要秘钥。
代码:MD5Util
package MD5;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {public static void main(String[] args) {String sha256Str = getSha256Str("123456");
System.out.println(sha256Str);
}
/**
* sha256 加密
*
* @param str 要加密的字符串
* @return 加密后的字符串
*/
public static String getSha256Str(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
}
return encodeStr;
}
/**
* sha256 加密 将 byte 转为 16 进制
*
* @param bytes 字节码
* @return 加密后的字符串
*/
private static String byte2Hex(byte[] bytes) {StringBuilder stringBuilder = new StringBuilder();
String temp;
for (byte aByte : bytes) {temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
// 1 失去一位的进行补 0 操作
stringBuilder.append("0");
}
stringBuilder.append(temp);
}
return stringBuilder.toString();}
}
SHA-256 算法:
sha256 算法也是一种明码散列函数,对于任意长度的音讯,SHA256 都会产生一个 256bit 长的散列值(哈希值),用于确保信息传输残缺统一,称作音讯摘要。这个摘要相当于是个长度为 32 个字节的数组,通常用一个长度为 64 的十六进制字符串来示意。
和 MD5 算法比照
相同点:
1、都是明码散列函数,加密不可逆。
2、都能够实现对任意长度对象加密,都不能避免碰撞。
安全性方面:
1、SHA256(称 SHA2)的安全性最高,然而耗时要其余两种多很多。
2、md5 相对来说比拟容易碰撞,安全性没这么高。
性能方面:
以个 60M 的件为测试样本,通过 1000 次的测试平均值,这两种算法的体现如下:
MD5 算法运 1000 次的均匀工夫为:226ms
SHA256 算法运 1000 次的均匀工夫为:473ms
总而言之,md5 和 sha256 都是明码散列函数,加密不可逆。尽管都不能避免碰撞,然而相对而言,md5 比拟容易碰撞,安全性没有 sha256 高。
代码:SHA256Util
package RSASha256;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Util {public static void main(String[] args) {String sha256Str = getSha256Str("123456");
System.out.println(sha256Str);
}
/**
* sha256 加密
*
* @param str 要加密的字符串
* @return 加密后的字符串
*/
public static String getSha256Str(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
}
return encodeStr;
}
/**
* sha256 加密 将 byte 转为 16 进制
*
* @param bytes 字节码
* @return 加密后的字符串
*/
private static String byte2Hex(byte[] bytes) {StringBuilder stringBuilder = new StringBuilder();
String temp;
for (byte aByte : bytes) {temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
// 1 失去一位的进行补 0 操作
stringBuilder.append("0");
}
stringBuilder.append(temp);
}
return stringBuilder.toString();}
}
三、国密算法介绍
简介:
为保障国家明码利用平安,2011 年 GJMM 管理局公布《对于做好公钥明码算法降级工作的告诉》,
要求自 2011 年 3 月 1 日起在建和拟建公钥明码基础设施电子认证零碎和密钥管理系统应应用国密算法。
《金融和重要畛域明码利用与翻新倒退工作布局(2018-2022 年)》以及相干法规文件也要求我国金融
和重要畛域明码利用采纳 SM2 国产明码算法体系。
国密算法是指 GJMM 管理局认定的一系列国产明码算法,包含 SM1-SM9 以及 ZUC 等。
其中 SM1、SM4、SM5、SM6、SM7、SM8、ZUC 等属于对称明码,SM2、SM9 等属于公钥明码,SM3 属于单向散列函数。
目前我国次要应用公开的 SM2、SM3、SM4 作为商用明码。
SM1:
SM1 也叫商密 1 号算法,是一种国产的对称算法,分组长度和密钥长度都为 128 比特,该算法不公开,调用该算法时,须要通过加密芯片的接口进行调用。算法平安窃密强度及相干软硬件实现性能与 AES 相当
SM2:
SM2 算法和 RSA 算法都是公钥明码算法,SM2 算法是一种更先进平安的算法,在咱们国家商用明码体系中被用来替换 RSA 算法。
随着明码技术和计算机技术的倒退,目前罕用的 1024 位 RSA 算法面临重大的平安威逼,咱们国家明码治理部门通过钻研,决定采纳 SM2 椭圆曲线算法替换 RSA 算法。
代码:SM2Util
package GMSM;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
/**
* SM2 工具类
* */
public class SM2Util {
//SM2 举荐曲线名称
static String SM2_CURVE_NAME = "sm2p256v1";
public static final Charset UTF_8 = Charset.forName("utf-8");
/**
* 生成密钥
*
* @return
* @throws Exception
*/
public static KeyPair genKeyPair() throws Exception {final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM2_CURVE_NAME);
// 获取一个椭圆曲线类型的密钥对生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
SecureRandom random = new SecureRandom();
// 应用 SM2 的算法区域初始化密钥生成器
kpg.initialize(sm2Spec, random);
// 获取密钥对
KeyPair keyPair = kpg.generateKeyPair();
return keyPair;
}
/**SM2 依据公钥加密
* param: message 待加密内容,publicKey 加密公钥(BASE64 编码)* return: 加密信息的 Base64 编码
* @throws InvalidCipherTextException
* */
public static String encryptBySM2(String message, String publicKey) throws InvalidCipherTextException {ECDomainParameters domin = getDomain();
// 公钥对象
ECPublicKeyParameters pubKeyParameters = getPubKey(publicKey,domin);
byte[] cipherBytes = new byte[0];
cipherBytes = encrypt(SM2Engine.Mode.C1C3C2, pubKeyParameters, message.getBytes(UTF_8));
return Base64.toBase64String(cipherBytes);
}
/**
* SM2 依据私钥解密
* param:cipherText 待解密密文 privateKey- 私钥(BASE64 编码)* */
public static String decryptBySM2(String cipherText, String privateKey) throws InvalidCipherTextException {BigInteger d = new BigInteger(1,Base64.decode(privateKey));
ECDomainParameters domin = getDomain();
// 私钥对象
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(d, domin);
byte[] decrypt = decrypt(SM2Engine.Mode.C1C3C2, ecPrivateKeyParameters, Base64.decode(cipherText));
return new String(decrypt,UTF_8);
}
/**
* 依据公钥字符串创立公钥对象
*
* */
public static ECPublicKeyParameters getPubKey(String publicKey, ECDomainParameters domain) {ECCurve curve = domain.getCurve();
ECPoint point = curve.decodePoint(Base64.decode(publicKey));
ECPublicKeyParameters PublicKey = new ECPublicKeyParameters(point, domain);
return PublicKey;
}
/**
* @param mode 指定密文构造,旧规范的为 C1C2C3,新的 [《SM2 明码算法应用标准》GM/T 0009-2012] 规范为 C1C3C2
* @param pubKeyParameters 公钥
* @param srcData 原文
* @return 依据 mode 不同,输入的密文 C1C2C3 排列程序不同。C1 为 65 字节第 1 字节为压缩标识,这里固定为 0x04,前面 64 字节为 xy 重量各 32 字节。C3 为 32 字节。C2 长度与原文统一。* @throws InvalidCipherTextException
*/
public static byte[] encrypt(SM2Engine.Mode mode, ECPublicKeyParameters pubKeyParameters, byte[] srcData)
throws InvalidCipherTextException {SM2Engine engine = new SM2Engine(mode);
ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom());
engine.init(true, pwr);
return engine.processBlock(srcData, 0, srcData.length);
}
/**
* @param mode 指定密文构造,旧规范的为 C1C2C3,新的 [《SM2 明码算法应用标准》GM/T 0009-2012] 规范为 C1C3C2
* @param priKeyParameters 私钥
* @param sm2Cipher 依据 mode 不同,须要输出的密文 C1C2C3 排列程序不同。C1 为 65 字节第 1 字节为压缩标识,这里固定为 0x04,前面 64 字节为 xy 重量各 32 字节。C3 为 32 字节。C2 长度与原文统一。* @return 原文。SM2 解密返回了数据则肯定是原文,因为 SM2 自带校验,如果密文被篡改或者密钥对不上,都是会间接报异样的。* @throws InvalidCipherTextException
*/
public static byte[] decrypt(SM2Engine.Mode mode, ECPrivateKeyParameters priKeyParameters, byte[] sm2Cipher)
throws InvalidCipherTextException {SM2Engine engine = new SM2Engine(mode);
engine.init(false, priKeyParameters);
return engine.processBlock(sm2Cipher, 0, sm2Cipher.length);
}
public static ECDomainParameters getDomain() {
// 获取一条 SM2 曲线参数
X9ECParameters x9ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
// 结构 domain 参数
ECDomainParameters domain = new ECDomainParameters(x9ECParameters.getCurve(),
x9ECParameters.getG(),
x9ECParameters.getN(),
x9ECParameters.getH());
return domain;
}
/**
* 私钥签名
* @param privateKey 私钥
* @param content 待签名内容
* @return
*/
public static String sign(String privateKey, String content) throws CryptoException, CryptoException {
// 待签名内容转为字节数组
byte[] message = content.getBytes();
BigInteger domainParameters = new BigInteger(1,Base64.decode(privateKey));
ECDomainParameters domin = getDomain();
// 私钥对象
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(domainParameters, domin);
// 创立签名实例
SM2Signer sm2Signer = new SM2Signer();
// 初始化签名实例, 带上 ID, 国密的要求,ID 默认值:1234567812345678
try {sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
} catch (NoSuchAlgorithmException e) {e.printStackTrace();
}
sm2Signer.update(message, 0, message.length);
// 生成签名, 签名分为两局部 r 和 s, 别离对应索引 0 和 1 的数组
byte[] signBytes = sm2Signer.generateSignature();
String sign = Base64.toBase64String(signBytes);
return sign;
}
/**
* 验证签名
* @param publicKey 公钥
* @param content 待签名内容
* @param sign 签名值
* @return
*/
public static boolean verify(String publicKey, String content, String sign) {
// 待签名内容
byte[] message = content.getBytes();
byte[] signData = Base64.decode(sign);
// 获取一条 SM2 曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM2_CURVE_NAME);
// 结构 domain 参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());
// 提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(java.util.Base64.getDecoder().decode(publicKey));
// 公钥后面的 02 或者 03 示意是压缩公钥,04 示意未压缩公钥, 04 的时候,能够去掉后面的 04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
// 创立签名实例
SM2Signer sm2Signer = new SM2Signer();
ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));
sm2Signer.init(false, parametersWithID);
sm2Signer.update(message, 0, message.length);
// 验证签名后果
boolean verify = sm2Signer.verifySignature(signData);
return verify;
}
}
SM3:
国产哈希算法,也叫音讯摘要算法,能够用 MD5 作为比照了解。该算法已公开。校验后果为 256 位。SM3 是中华人民共和国政府采纳的一种明码散列函数规范,由 GJMM 管理局于 2010 年 12 月 17 日公布。相干规范为“GM/T 0004-2012《SM3 明码杂凑算法》”。
在商用明码体系中,SM3 次要用于数字签名及验证、音讯认证码生成及验证、随机数生成等,其算法公开。据 GJMM 管理局示意,其安全性及效率与 SHA-256 相当。
代码:SM3Util
package GMSM;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
import java.util.Arrays;
public class SM3Util {
/**
* 计算 SM3 摘要值
*
* @param srcData 原文
* @return 摘要值,对于 SM3 算法来说是 32 字节
*/
public static byte[] hash(byte[] srcData) {SM3Digest digest = new SM3Digest();
digest.update(srcData, 0, srcData.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return hash;
}
/**
* 验证摘要
*
* @param srcData 原文
* @param sm3Hash 摘要值
* @return 返回 true 标识验证胜利,false 标识验证失败
*/
public static boolean verify(byte[] srcData, byte[] sm3Hash) {byte[] newHash = hash(srcData);
System.out.println(Hex.toHexString(newHash));
System.out.println(Hex.toHexString(sm3Hash));
if (Arrays.equals(newHash, sm3Hash)) {return true;} else {return false;}
}
}
SM4:
无线局域网规范的分组数据算法。对称加密,密钥长度和分组长度均为 128 位。对标 AES
代码:SM4Util
package GMSM;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
public class SM4Util {
static {Security.addProvider(new BouncyCastleProvider());
}
public static final String ALGORITHM_NAME = "SM4"; // AES
public static final String DEFAULT_KEY = "random_seed";
// 128-32 位 16 进制;256-64 位 16 进制
public static final int DEFAULT_KEY_SIZE = 128;
public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {return generateKey(DEFAULT_KEY, DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(String seed) throws NoSuchAlgorithmException, NoSuchProviderException {return generateKey(seed, DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(String seed, int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
if (null != seed && !"".equals(seed)) {random.setSeed(seed.getBytes());
}
kg.init(keySize, random);
return kg.generateKey().getEncoded();
}
/**
* @description 加密
*/
public static byte[] encrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {return sm4core(algorithmName, Cipher.ENCRYPT_MODE, key, iv, data);
}
/**
* @description 解密
*/
public static byte[] decrypt(String algorithmName, byte[] key, byte[] iv, byte[] data) throws Exception {return sm4core(algorithmName, Cipher.DECRYPT_MODE, key, iv, data);
}
private static byte[] sm4core(String algorithmName, int type, byte[] key, byte[] iv, byte[] data) throws Exception {Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
if (algorithmName.contains("/ECB/")) {cipher.init(type, sm4Key);
} else {if(iv == null){cipher.init(type, sm4Key);
}else{IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(type, sm4Key, ivParameterSpec);
}
}
return cipher.doFinal(data);
}
}
四、各类领取零碎常见加密形式组合介绍
在领取零碎交互中,有多种多样的加密形式组合,这里就简略介绍两钟罕用的。
1. 传统形式
用可逆,非对称算法 (rsa,sm2 等) 应用对方公钥对报文内的要害信息进行加密,失去的密文进行编码,而后再对密文编码后的串 加盐 后应用不可逆算法 (sha256,md5,sm3 等) 进行签名,应用这些算法签名后失去的是一个 16 进制的串,签名放到另一个字段,个别是和加密后的信息并列的。
步骤:
1. 应用对方的 RSA 公钥对明文进行加密,失去的密文进行 base64 编码,作为 data
2. 将 data 的值加上约定好的盐,应用 sha256 算法进行签名,失去的签名是 16 进制的串,放到 sign
加密后的 json:
{
"data": "auwPDINowtdseZTfcCR8B1OPsevJE1MqIVOLn56WDvw3gjksSjkpMGp4lpYiqZkJ9G2PTaYC2DZSiyhbjtLvfnJE54mZz649gTWQVIHAn5fzV8Vs3JL3Kf9WB0Br9EbR07qrfGlWCvkkgktmTfDS/4qQGywn7TVQ3EH6HwQI7kUf0GlZ9CYcYHKi4/F61s6H9Hws+CPrhchGfJmHnu+835dSVS4IJOnA+0S5JXWVEN/is5i5cmxFb3RUYZbfpu+7JLDgcXLC8oNKIZxSE1aXhoFMj4CTTYCyRxKNCD015fnBMKh+7TYHEksyyybtVjkPqDeczgT3z+Qvj9vdyUZB1Q==",
"sign": "d3b36e7a837d6241445e506faf72c8d8e3467ee9c11efae0c67dfb5b15f44b6e"
}
接管后,先拿到密文,退出约定好的盐进行验签,验签通过后再用私钥进行解密。返回报文同理,只不过返回报文用的也是对方的公钥加密,对方接管后用本人的私钥解密。
demo:RSASha256Test
package RSASha256;
import java.util.HashMap;
import java.util.Map;
public class RSASha256Test {public static void main(String[] args) {Map<String,String> jsonData = new HashMap<>();
String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuazymB25s/ueIfwMM2H74736dQQE4bvRDgNfWltM4PzduNC84ntav41qEgbZtby47O57m/YDVf6vMXJH25ejlsBBhls7FD7xHTkOrskZqLekH0xCs2xa/akdwllbewUNzvWMuQy8X3j2VnrILXzVjvqYxkFY0itMo8fq1xSO6B/S5/RaeKpTtIepFB2cIU9vMrtLoCsnjVoTuPdWcoC79LtS0g1Q5BV4dn+Uca+8/gUbhS7vbth3oLZt6gXTRjJrPxhT4lqWsCZqwkQHPG/8JQgnFqs7+R0KcyemG/411IaZI9WYRGTsQji8sQ2q7HAlZRXvJn+As7jJPvyi67JGKwIDAQAB";
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC5rPKYHbmz+54h/AwzYfvjvfp1BAThu9EOA19aW0zg/N240Lzie1q/jWoSBtm1vLjs7nub9gNV/q8xckfbl6OWwEGGWzsUPvEdOQ6uyRmot6QfTEKzbFr9qR3CWVt7BQ3O9Yy5DLxfePZWesgtfNWO+pjGQVjSK0yjx+rXFI7oH9Ln9Fp4qlO0h6kUHZwhT28yu0ugKyeNWhO491ZygLv0u1LSDVDkFXh2f5Rxr7z+BRuFLu9u2Hegtm3qBdNGMms/GFPiWpawJmrCRAc8b/wlCCcWqzv5HQpzJ6Yb/jXUhpkj1ZhEZOxCOLyxDarscCVlFe8mf4CzuMk+/KLrskYrAgMBAAECggEABjC+8dVj4J1N+2IU4g2tQT2PQSF+LCx/3tC7+B49JO8pUUUcVwy3zNUhKTKzRXziSXv2ARAlslNIcgSWYrreiGMmjB00jgs/LLM/SxKHWXmt7iEzxBmjuvtNc7JY+3QCrti+9Vh4W1KEHAQB8opL8HVobIu3M2KgLoG20a7syM5gPiCUb3EQ6P30u47y7I2wsbMnMmu79rPu3Q7lSNeoB9KdVV6EA6vukL3chY1QxFh6oFZS1yNjFjxy5RxT8U5Juhr5LYRnOP8MEVptbtrh34SSm0uyckkgJ6jOsrDJqo/DJxjPxixj+BWgG5/XbNq3PFEyEAqkrTZY+FMHSufoaQKBgQDpqPxpl05mDUXCnc7I6F8LZqjnjZU1h9zwKuQe94Fs3I3siWS62sJx1pZ66P9eeIOsT0T4Ye9nDx2x04yyfUcENhydFTQ5M5M9e2q1kKdNeBUT6Xy4WcRibKozUq07mLYSn0kDbuimp/Erp2w3hl5nZu/eLKDjt7yM0otv3uPp5wKBgQDLbYC4hcvk+f1kd/xJQ2ljwF+c8kHHFrlLRipf7qTB24dcrauWXbAGbbcKXx2G+3Waswyf09NdALiWab/jUUUHJm0YGxyr0k5lHci0/eC7hD3tWTL/HRyeF3umKZEkBQypzNeymE6t0lSqjL3MXFQLqumu4h677qA9/DNh7DYhHQKBgQCfAm/bj6s7iba6jVfmozPi91bkVRaAWlgBXL7nT/nU0ncGzC0vd6WxgJ3hQORgLtU0krFV8pfP45qKpHNwGA8XD5gDUiW68500zuM8chdYgeqeJVvJvNUHQfnFeXMIRpFJNPqkCnrqxwk5cvMTCi7+YS/FW0uWDDiVAMcBN4aUawKBgCOgvAiVNk6WEfEEqqTSL6UOzjAYpbiOnEk4src2fpiNMDnlGMYvBmM51/LzEaLQa5p6fV2Ipd4GAE4nmznew+4qprSwGudk3+IJw1sfk7qDwKzPEIVpvddaWYeShB8A22TpwWVAE5eR3M459AvUp8ubVW4RoDxd4Ka6gu1Fh31pAoGAYNPKJNCrdOsQH0nkH0Ld44cH6HD+zcZtoad2eQk0T2SPnKBsIS1S9W0adreOXUv1edO/hN3P/BcTuCaax7ijTscS2f0atc/NIa6LjBnK7oUBBzib9v21L72ZVZ5st4c/H7IzbQCXfS81489a7TTHP+e1HzS/XePftO0pAkr1GJ0=";
String salt = "E4bvRDg";
System.out.println("公钥为:" + publicKey);
String plaintext = "{'a':'1111','b':'2222'}";
String ciphertext = null;
String sign = null;
try {ciphertext = RSAUtil.encrypt(plaintext,publicKey);
jsonData.put("data",ciphertext);
System.out.println("第一步,应用对方的 RSA 公钥对明文进行加密,失去的密文进行 base64 编码,作为 data:"+jsonData.get("data"));
sign = SHA256Util.getSha256Str(ciphertext+salt);
jsonData.put("sign",sign);
System.out.println("第二步,将 data 的值加上约定好的盐,应用 sha256 算法进行签名,失去的签名是 16 进制的串,放到 sign:"+jsonData.get("sign"));
} catch (Exception e) {System.out.println("加密失败");
throw new RuntimeException(e);
}
System.out.println("失去的密文和签名为:"+jsonData);
System.out.println("模仿另一方收到密文和签名之后..........");
System.out.println("私钥为:" + privateKey);
String ciphertext1 = jsonData.get("data");
String sign1 = jsonData.get("sign");
String deciphering1 = null;
if(sign1.equals(SHA256Util.getSha256Str(ciphertext1+salt))){System.out.println("签名验证胜利");
deciphering1 = RSAUtil.decrypt(ciphertext1,privateKey);
System.out.printf("数据解密胜利,data 值为:"+deciphering1);
}else{System.out.println("签名验证失败,解密失败");
}
}
}
2. 一次一密 + 签名身份验证
生成一个对称算法秘钥,应用该秘钥对明文进行加密,而后将该秘钥应用对方的公钥进行加密,加密后编码,而后再对明文应用本人的私钥进行签名,因为私钥只有本人有,所以用本人的私钥签名后,对方应用你提供的公钥进行验签,就能够验证你的身份。
步骤:
1. 随机生成一个 SM4 密钥;
2. 应用 SM4 密钥加密密文,失去的密文进行 base64 编码,作为 textToDecrypt
3. 将 SM4 密钥进行 base64 编码后,再应用对方的 SM2 公钥对这个 base64 串进行加密,失去的密文进行 base64 编码,作为 keyCiphertext;
4. 对第二步的明文应用第三方 sm2 私钥签名,失去的签名进行 base64 编码后作为 signature。
加密后 json:
{
"textToDecrypt": "cU0ymFMho3HXmVq0hwDHvZg9oLsuZT19GLBKcvHzdZ4=",
"signature": "MEUCIA8nO1g705B3zzmaYv7yK8jajz9r2fKuvSPZliY8k1xBAiEA0noAEiBos3fU7RwEt81jTjwlrQL+tbQfy77VK/h/ES4=",
"keyCiphertext": "BJv91ULAd6fNchtKF/+b1JW2o7il0Hjbr6erQrY96S8QrDrkE7Y6EpgzM/eY1OumNsr6VbRuK0wx6yo8fq3QapYHZU5X1LzjgyMC+AiCnhzut5V3xxz4Yd0M1Zx2D4ljaOnJTDED4nS5kU2+C5VFF81DyzVDmWw6ZQ=="
}
对方接管后,依照以上步骤再进行验签,解密等操作。
demo:GMSMTest
package GMSM;
import java.util.HashMap;
import java.util.Map;
import java.util.Base64;
public class GMSMTest {public static void main(String[] args) throws Exception {
// 你这边拿到的 sm2 公私钥,对方的公钥和本人的私钥
String priK = "AMz5m/WOgpgYiXnlckC7+zdvZXnQYMBcWXt1n7khfdVO";
String otherPubK = "BJkVGU87s7xhLrFCI8MGPzp2X+lizte+2So52CQ1tVEDDqOe+Q4vdQXglV2JtIOZeWc/bE8Rzdo29svmTj8+7mA=";
String messge = "{'aaa':'111','bbb':'222'}";
Map<String, String> jsonData = new HashMap<>();
byte[] keyB = SM4Util.generateKey(null);
String key = Base64.getEncoder().encodeToString(keyB);
String algorithmName = ("SM4/ECB/PKCS5PADDING");
System.out.println("第一步,随机生成一个 SM4 秘钥:"+key);
System.out.println("选定算法:"+algorithmName);
// 用随机生成的 SM4 秘钥加密
byte[] encryptMSG = SM4Util.encrypt(algorithmName, keyB, null, messge.getBytes());
jsonData.put("textToDecrypt",Base64.getEncoder().encodeToString(encryptMSG));
System.out.println("第二步,应用 SM4 密钥加密明文,失去的密文进行 base64 编码,作为 textToDecrypt :"+jsonData.get("textToDecrypt"));
// 将随机生成的 SM4 秘钥,应用对方的 SM2 公钥进行加密
String keyCiphertext = SM2Util.encryptBySM2(key, otherPubK);
jsonData.put("keyCiphertext",keyCiphertext);
System.out.println("第三步, 将 SM4 密钥进行 base64 编码后,再应用对方的 SM2 公钥对这个 base64 串进行加密,失去的密文进行 base64 编码,作为 keyCiphertext;"+jsonData.get("keyCiphertext"));
// 对明文应用本人的 sm2 私钥进行签名 如果有须要,能够加点盐
String signature = SM2Util.sign(priK,messge);
jsonData.put("signature",signature);
System.out.println("第四步, 对第二步的明文应用对方 sm2 私钥签名,失去的签名进行 base64 编码后作为 signature:"+jsonData.get("signature"));
System.out.println("残缺 json 串:"+jsonData);
// 模仿接到 json 传之后的解决
// 对方拿到的,你的公钥,和对方本人的私钥
String PubK = "BNTFr3oo1xm3biGoQQnk20QJfbFvK4HNFgs1DkZJuUT9pW8I7K0D9L9P4mW+tQhWwfJlD/Nsx3BY+oriF1B25bA=";
String otherPriK = "I0sHzcDm+oS2FIAHFLxxkWJi1AbrHRxEYs1AxpZTlNw=";
// 将 sm4 秘钥进行解密
String keyCiphertext1 = SM2Util.decryptBySM2(jsonData.get("keyCiphertext"), otherPriK);
System.out.println("对方应用 sm2 私钥解密 sm4 秘钥:"+keyCiphertext1);
// 通过 sm4 秘钥解密数据密文
byte[] textToDecrypts = SM4Util.decrypt(algorithmName, Base64.getDecoder().decode(keyCiphertext1), null, Base64.getDecoder().decode(jsonData.get("textToDecrypt").getBytes()));
String textToDecryptsS = new String(textToDecrypts);
System.out.println("对方应用 sm4 秘钥解密后的数据:"+ textToDecryptsS);
// 验证签名
boolean proving = SM2Util.verify(PubK, textToDecryptsS, jsonData.get("signature"));
System.out.println("验证签名是否通过:"+proving);
}
}
国密依赖 pom
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>