密码控件解密流程和原理
近期做了一些密码控件相关的需求,这里整理一下密码控件后端服务的加解密原理,以及引申介绍了一些二进制和base64编码的一些关系,其实加解密原理离不开https,本文没有描写https的部分,大家有空也可以了解一下.
1.1 RSA
在客户端的键盘安全控件内部实现网银会话绑定加密功能,主要功能实现如下,根据上图流程所示,服务器端生成一个公私钥对(Kp,Ks),键盘安全控件内置其公钥Kp。建立会话时,在客户端,安全控件内部生成随机数Rc,在服务器端生成随机数Rs。随后由服务器维护随机数Rs与会话的绑定,并将随机数Rs发给 客户端。客户端安全控件接收Rs后,将Rs与自己得Rc进行拼接,得到新的随机数Rs+Rc=R。此随机数R将作为后面的数据的对称加密的密钥。首先,在安全控件内部,用R对文明数据Dp(键盘输入的密码口令等)进行对称加密,形成密文Dc,加密算法采用DES等对称算法。其次,将客户端自己生成的随机数Rc用服务器的公钥进行加密。最后,将用公钥加密后Rc、以及用R加密后的数据一同发到服务器端。
服务器端收到数据后,用自己的私钥对Rc密文进行解密后得到Rc,然后用保存的Rs与Rc进行拼接得到R,再用R对数据密文进行解密。
1.2 国密
如果加密算法标示为国产算法,加密方式为:客户端产生16字节随机数Rc,服务器产生16字节随机数Rs。取Rs的前8字节在前以及Rc的前8字节在后,组成16字节的对称密钥R。取Rs的后8字节在前以及Rc的后8字节在后,组成16字节作为初始向量。以16字节为加密单位块长度、PKCS#7 填充模式对密码使用SM4加密。SM4加密采用CBC模式。
2.1随机数生成
随机数生成代码
Base64.toBase64String(SecureRandoms.getInstance().genBytes(16));
import java.security.SecureRandom;
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
random.nextBytes(bytes);
String toBase64String = Base64.toBase64String(bytes);
3引申知识hex和base64
hex和base64 都是 把二进制数组变成可见字符的编码方式,在公钥,私钥保存成文本,生成签名值都会用到.
3.1hex
1.使用16个可见字符来表示一个二进制数组,编码后数据大小将x2
2.1个字符需要用2个可见字符来表示
/**
* 转换字节数组为16进制字串
*/
public static String byteArrayToHexString(byte[] bytes) {
String ret = "";
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
ret += hex.toUpperCase();
}
return ret;
}
/**
* 将Hex String转换为Byte数组
*
* @param hexString the hex string
* @return the byte [ ]
*/
public static byte[] hexStringToBytes(String hexString) {
if (StringUtils.isEmpty(hexString)) {
return null;
}
hexString = hexString.toLowerCase();
final byte[] byteArray = new byte[hexString.length() >> 1];
int index = 0;
for (int i = 0; i < hexString.length(); i++) {
if (index > hexString.length() - 1) {
return byteArray;
}
byte highDit = (byte) (Character.digit(hexString.charAt(index), 16) & 0xFF);
byte lowDit = (byte) (Character.digit(hexString.charAt(index + 1), 16) & 0xFF);
byteArray[i] = (byte) (highDit << 4 | lowDit);
index += 2;
}
return byteArray;
}
3.2 base64
1.使用64个可见字符来表示一个二进制数组,编码后数据大小变成原来的4/3
2.3个字符用4个可见字符来表示
常用的工具类:
-
java.util.Base64(数据存在换行,会报错)
jdk8内置的Base64解码器进行解析的时候,会抛出java.lang.IllegalArgumentException: Illegal base64 character a异常.
这个类包含了base64编码格式的编码方法和解码方法,而且实现是按照rfc4648和rfc2045两个协议来实现的。
编码和解码操作是照着两个协议中的’Table 1’中指定的’The Base64 Alphabet’来的。编码器不会添加任何换行符,解码器只会处理’The Base64 Alphabet’范围内的数据,如果不在这个范围内,解码器会拒绝处理。 -
org.apache.commons.codec.binary.Base64.decodeBase64(这个兼容性更好,建议使用这个.)
Apache Common中的org.apache.commons.codec.binary.Base64类是基于rfc2045实现的,根据类注释可以了解到此实现解码时忽略了所有不在the base64 alphabet范围内的字符,所以该实现可以处理包含换行符的base64编码结果。
Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get("C:\\Program Files (x86)\\360\\360zip\\utils\\360Feedback.exe")));
Base64.getDecoder().decode(reqVO.getPhoto().replaceAll("-", "+"));
# apache的base64工具类
org.apache.commons.codec.binary.Base64.decodeBase64(reqVO.getPhoto().replaceAll("-", "+"));
maven坐标
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.2</version>
</dependency>
4.国产密码算法
国密即国家密码局认定的国产密码算法。主要有SM1,SM2,SM3,SM4。密钥长度和分组长度均为128位。
SM1为对称加密。其加密强度与AES相当。该算法不公开,调用该算法时,需要通过加密芯片的接口进行调用。
SM2为非对称加密,基于ECC。该算法已公开。由于该算法基于ECC,故其签名速度与秘钥生成速度都快于RSA。ECC 256位(SM2采用的就是ECC 256位的一种)安全强度比RSA 2048位高,但运算速度快于RSA。
SM3消息摘要。可以用MD5作为对比理解。该算法已公开。校验结果为256位。
SM4无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位。
不太严谨的使用方式类比:
国产密码算法 | 开源密码算法 | 加密方式 |
---|---|---|
SM1 | AES | 对称加密 |
SM2 | RSA | 非对称加密 |
SM3 | MD5/SHA-256 | 消息摘要 |
SM4 | 3DES | 对称加密 |
参考文档
- https://www.jianshu.com/p/0636ee193e7f?tdsourcetag=s_pcqq_aiomsg
- https://github.com/ZZMarquis/gmhelper
- https://baike.baidu.com/item/SM4.0/3901780?fr=aladdin
- 使用java8的java.util.Base64报“java.lang.IllegalArgumentException: Illegal base64 character d”的问题
如果觉得文章对您有用,可以关注我的公众号 程序和猫 , 更加方便的交流.