京东科技 姚永健
一、术语表:
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位的信息摘要。
不可逆加密算法{BANNED}最佳大的特点就是不需要密钥
5.加密盐
加密盐也是比较常听到的一个概念,盐就是一个随机字符串用来和我们的加密串拼接后进行加密。加盐主要是为了保证加密字符串的安全性。假如有一个加盐后的加密串,黑客通过一定手段得到这个加密串,他解密后拿到的明文,并不是我们加密前的字符串,而是加密前的字符串和盐组合的字符串,这样相对来说又增加了字符串的安全性
或者也可以用在签名,例如签名是对明文或者密文加盐后的签名,有人想串改数据,如果不知道这个盐和规则,那么接收方验签就会不通过,从而保证通讯的安全。
二、传统加密算法介绍
DES(Data Encryption Standard):
对称算法,数据加密标准,速度较快,适用于加密大量数据的场合。
AES算法:
是DES的升级版,属于对称算法。可逆
代码:AESUtil
RSA算法:
公钥加密算法,非对称,可逆
代码:RSAUtil
MD5算法:
信息摘要(Hash安全散列)算法,也叫哈希算法,哈希值也叫散列值,不可逆,不需要秘钥。
代码: MD5Util
SHA-256算法:
sha256算法也是一种密码散列函数,对于任意长度的消息,SHA256都会产生一个256bit长的散列值(哈希值),用于确保信息传输完整一致,称作消息摘要。这个摘要相当于是个长度为32个字节的数组,通常用一个长度为64的十六进制字符串来表示。
和MD5算法对比
相同点:
1、都是密码散列函数,加密不可逆。
2、都可以实现对任意长度对象加密,都不能防止碰撞。
安全性方面:
1、SHA256(称SHA2)的安全性{BANNED}最佳高,但是耗时要其他两种多很多。
2、md5相对来说比较容易碰撞,安全性没这么高。
性能方面:
以个60M的件为测试样本,经过1000次的测试平均值,这两种算法的表现如下:
MD5算法运1000次的平均时间为:226ms
SHA256算法运1000次的平均时间为:473ms
总而言之,md5和sha256都是密码散列函数,加密不可逆。虽然都不能防止碰撞,但是相对而言,md5比较容易碰撞,安全性没有sha256高。
代码:SHA256Util
三、国密算法介绍
简介:
为保障国家密码应用安全,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
SM4:
无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。对标AES
代码:SM4Util
四、各类支付系统常见加密方式组合介绍
在支付系统交互中,有多种多样的加密方式组合,这里就简单介绍两钟常用的。
1.传统方式
用可逆,非对称算法(rsa,sm2等)使用对方公钥对报文内的关键信息进行加密,得到的密文进行编码,然后再对密文编码后的串加盐后使用不可逆算法(sha256,md5,sm3等)进行签名,使用这些算法签名后得到的是一个16进制的串,签名放到另一个字段,一般是和加密后的信息并列的。
步骤:
1.使用对方的RSA公钥对明文进行加密,得到的密文进行base64编码,作为data
2.将data的值加上约定好的盐,使用sha256算法进行签名,得到的签名是16进制的串,放到sign
加密后的json:
接收后,先拿到密文,加入约定好的盐进行验签,验签通过后再用私钥进行解密。返回报文同理,只不过返回报文用的也是对方的公钥加密,对方接收后用自己的私钥解密。
demo:RSASha256Test
2. 一次一密+签名身份验证
生成一个对称算法秘钥,使用该秘钥对明文进行加密,然后将该秘钥使用对方的公钥进行加密,加密后编码,然后再对明文使用自己的私钥进行签名,因为私钥只有自己有,所以用自己的私钥签名后,对方使用你提供的公钥进行验签,就可以验证你的身份。
步骤:
1. 随机生成一个SM4密钥;
2. 使用SM4密钥加密密文,得到的密文进行base64编码,作为textToDecrypt
3. 将SM4密钥进行base64编码后,再使用对方的SM2公钥对这个base64串进行加密,得到的密文进行base64编码,作为keyCiphertext;
4. 对第二步的明文使用第三方sm2私钥签名,得到的签名进行base64编码后作为signature。
加密后json:
对方接收后,按照以上步骤再进行验签,解密等操作。
demo:GMSMTest
国密依赖pom