前言
据记载,公元前400年,古希腊人发明了置换密码。1881年世界上的第一个电话保密专利出现。在第二次世界大战期间,德国军方启用“恩尼格玛”密码机,密码学在战争中起着非常重要的作用。
随着信息化和数字化社会的发展,人们对信息安全和保密的重要性认识不断提高,于是在1997年,美国国家标准局公布实施了“美国数据加密标准(DES)”,民间力量开始全面介入密码学的研究和应用中,采用的加密算法有DES、RSA、SHA等。
如果你是Java开发工程师,那么你或多或少了解使用Java加解密,加签验签的api,这些也是一个Java工作者必备的技能之一。让我们一起来讨论下常用加解密技术。
背景
在日常工作中,最近对接银行需求比较多,因此对报文的加解密是一件必须做的事情,但是对接不同银行所用的加解密标准或者是使用算法不同,因此我不得不去尽可能了解更多的加解密算法和其中java API,现在我把这些了解的东西分享给大家,一起丰富我们的知识储备。
java加解密介绍
1.加密和解密
1.1. 加密
数据加密的基本过程,就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为 “密文”。通过这样的途径,来达到保护数据不被非法人窃取、阅读的目的。
1.2. 解密
加密的逆过程为解密,即将该编码信息转化为其原来数据的过程。
1.3 算法分类
根据加密结果是否可以被解密,算法可以分为可逆加密和不可逆加密(单向加密),从这个意义上来说,单向加密只能称之为加密算法而不是加解密算法。对于可逆加密,又可以根据密钥的的对称性分为对称加密和非对称加密。具体的分类结构如下:
- 可逆加密
- 对称加密:DES,3DES,AES,PBE
- 非对称加密:RSA,DSA,ECC
- 不可逆加密(单向加密):MD5,SHA,HMAC
2.模式介绍
分组密码(block cipher)是每次只能处理特定长度的一块数据的一类密码算法,这里的“一块”就称为分组(block)。此外,一个分组的比特数就称为分组长度(block length)。例如,DES和三重DES的分组长度都是64比特。这些密码算法一次只能加密64比特的明文,并生成64比特的密文。AES的分组长度可以从128比特、192比特和256比特中进行选择。当选择128比特的分组长度时,AES一次可加密128比特的明文,并生成128比特的密文。
流密码(stream cipher)是对数据流进行连续处理的一类密码算法。流密码中一般以1比特、8比特或32比特等为单位进行加密和解密。分组密码处理完一个分组就结束了,因此不需要通过内部状态来记录加密的进度;相对地,流密码是对一串数据流进行连续处理,因此需要保存内部状态。
对于可逆加密,在加密时可以选择加密模式,可以理解为加密算法可以有不同的工作方式,不同的工作方式之间存在效率、方式等方面的区别。要注意的是,对同一个数据,加密选择的模式与解密选择的模式必须相同,否则解密得不到正确的结果。主要的加密模式ECB (电子密码本模式)、CBC(分组连接模式)、CFB(密码反馈模式)、OFB (输出反馈模式) CTR计算器模式
- 什么是模式
2.1 ECB (电子密码本模式):
其使用方式是一个明文分组加密成一个密文分组,相同的明文分组永远被加密成相同的密文分组。直接利用加密算法分别对每个64位明文分组使用相同的64位密钥进行加密。每个明文分组的处理是相互独立的。这种模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
缺点:在给定密钥k 下,同一明文组总是产生同一密文组,这会暴露明文组的数据格式。某些明文的数据格式会使得明文组有大量的重复或较长的零串,一些重要的数据常常会在同一位置出现,特别是格式化的报头、作业号、发报时间、地点等特征都将被泄露到密文之中,使攻击者可以利用这些特征。
优点:用同个密钥加密的单独消息,其结果是没有错误传播。实际上,每一个分组可被看作是用同一个密钥加密的单独消息。密文中数据出了错,解密时,会使得相对应的整个明文分组解密错误,但它不会影响其他明文。然而,如果密文中偶尔丢失或添加一些数据位,那么整个密文序列将不能正确的解密。除非有某帧结构能够重新排列分组的边界。
2.2 CBC(分组连接模式):
对于相同的明文,加密结果不同。这就加大了密码破解者的破译难度。在密钥固定不变的情况下,改变每个明文组输入的链接技术,这样, 密文组不仅与当前的明文组有关,而且通过反馈的作用还与以前的明文组有关。这从密码学的本质上来说是一种混淆操作。这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
优点:能隐蔽明文的数据模式; 在某种程度上能防止数据篡改, 诸如明文组的重放,嵌入和删除等.
缺点:会出现错误传播(errorpropagation). 密文中任一位发生变化会涉及后面一些密文组. 但CBC 模式的错误传播不大, 一个传输错误至多影响两个消息组的接收结果,错误传播最多持续2个分组
3.密钥介绍
密钥在加解密算法中是一个参数,其长度根据不同的算法有所不同,同一算法的密钥长度也有可能有不同的要求。一般来说,密钥的长度与安全性成正比。在使用时,将明文(或密文)连同密钥放入相应的加密(或解密容器),即可得到密文(或明文),实现加解密。
keyGenerator:秘钥生成器,也就是更具算法类型随机生成一个秘钥,例如HMAC,所以这个大部分用在非可逆的算法中KeyFactory:秘密秘钥工厂,言外之意就是需要根据一个秘密(password)去生成一个秘钥,例如DES,PBE,所以大部分使用在对称加密中KeyPairGenerator:秘钥对生成器,也就是可以生成一对秘钥,也就是公钥和私钥,所以大部分使用在非对称加密中
1.keyGenerator
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORIGTHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}2.KeyFactory
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));3.KeyPairGenerator
类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
4.填充方式介绍
ZeroPadding,数据长度不对齐时使用0填充,否则不填充。
PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。
PKCS5Padding,PKCS7Padding的子集,块大小固定为8字节。
5.算法介绍
1.单向加密
前面说过,单向加密的结果是不可以被解密的,因此,单向加密的主要用途并不是传统意义上的加解密工作,而是对明文数据的保密和摘要提取。单向加密主要有MD5、SHA、HMAC等算法。
MD5:
MD5是应用最广泛的一种单向加密算法,其在数据加密、安全访问认证和文件完整性验证等方面都有应用。MD5加密输出是一个128位的十六进制数字串。
SHA:
SHA实际上是一组加密算法的合称,包括SHA-1,SHA-256,SHA-384,SHA-512。其中应用最广的是SHA-1,HTTPS中使用的HASH散列函数多使用SHA-1。相比较于MD5,SHA族有更高的安全性,到目前为止还没有人能破译其加密结果,但其加密速度比MD5慢,是以速度换取了安全性
/**
*
* @param strSrc 需要被加密的字符串
* @param encName 加密方式,有 MD5、SHA-1和SHA-256 这三种加密方式
* @return 返回加密后的字符串
*/
private static String EncryptStr(String strSrc, String encName) {
MessageDigest md = null;
String strDes = null;
byte[] bt = strSrc.getBytes();
try {
if (StringUtils.isBlank(encName)) {
encName = "MD5";
}
md = MessageDigest.getInstance(encName);
md.update(bt);
strDes = bytes2Hex(md.digest()); // to HexString
} catch (NoSuchAlgorithmException e) {
System.out.println("Invalid algorithm.");
return null;
}
return strDes;
}
2.对称加密
对称加密算法,应用的时间比较早,技术相对来说比较成熟,在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。对称加密算法的特点是算法公开、计算量小。不足之处是,交易双方都使用同样钥匙,安全性得不到保证。
- 特点:
- 密钥较小(一般小于256bit),密钥越大,加密越强,但加密解密越慢
- 优点:算法公开、计算量小、加密速度快、加密效率高,适用于大量数据的加密
- 缺点:密钥分配与管理,安全性较低
- 四种算法DES,3DES,AES,PBE
1.DES:
DES算法是一种分组加密机制,将明文分成N个组,然后对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位。
/**
* 加密
* @param datasource byte[]
* @param password String
* @return byte[]
*/
public static byte[] encrypt(byte[] datasource, String password) {
try{
SecureRandom random = new SecureRandom();
DESKeySpec desKey = new DESKeySpec(password.getBytes());
//创建一个密匙工厂,然后用它把DESKeySpec转换成密钥
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(desKey);
//Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance("DES");
//用密匙初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, random);
//现在,获取数据并加密
//正式执行加密操作
return cipher.doFinal(datasource);
}catch(Throwable e){
e.printStackTrace();
}
return null;
}
/**
* 解密
* @param src byte[]
* @param password String
* @return byte[]
* @throws Exception
*/
public static byte[] decrypt(byte[] src, String password) throws Exception {
// DES算法要求有一个可信任的随机数源
SecureRandom random = new SecureRandom();
// 创建一个DESKeySpec对象
DESKeySpec desKey = new DESKeySpec(password.getBytes());
// 创建一个密匙工厂
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
// 将DESKeySpec对象转换成SecretKey对象
SecretKey securekey = keyFactory.generateSecret(desKey);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance("DES");
// 用密匙初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, random);
// 真正开始解密操作
return cipher.doFinal(src);
}
2.3DES:
3DES算法是在DES的基础上发展出来的。在一些对安全性要求较高的场景下,DES的64位密钥安全性不能满足要求,于是人们采取了一种“简单暴力”的办法——三重数据加密,对数据进行加密,这样来说,破解的概率就小了很多。3DES的密钥长度为168位。由于3DES与DES的使用极其相似,只是密钥的长度有所改变,这里就不展开介绍了,感兴趣的读者可以尝试使用相应接口。
/**
* DESede 加密操作
*
* @param content
* 待加密内容
* @param key
* 加密密钥
* @return 返回Base64转码后的加密数据
*/
public static String encrypt(String content, String key) {
try {
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
// 初始化为加密模式的密码器
byte[] result = cipher.doFinal(byteContent);// 加密
return Base64.encodeBase64String(result);// 通过Base64转码返回
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* } } DESede 解密操作
*
* @param content
* @param key
* @return
*/
public static String decrypt(String content, String key) {
try {
// 实例化
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); // 使用密钥初始化,设置为解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key)); // 执行操作
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result, "utf-8");
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
3.AES:
AES 加密算法作为新一代的数据加密标准汇聚了强安全性、高性能、高效率、易用和灵活等优点。AES 设计有三个密钥长度:128,192,256 位。是目前可获得的最安全的加密算法。
/**
* @param content 加密前原内容
* @param pkey 长度为16个字符,128位
* @return base64EncodeStr aes加密完成后内容
* @throws
* @Title: aesEncryptStr
* @Description: aes对称加密
*/
public static String aesEncryptStr(String content, String pkey) throws UnsupportedEncodingException {
content=Base64.encodeBase64String(content.getBytes("UTF-8"));
byte[] aesEncrypt = aesEncrypt(content, pkey);
System.out.println("加密后的byte数组:" + Arrays.toString(aesEncrypt));
String base64EncodeStr = Base64.encodeBase64String(aesEncrypt);
System.out.println("加密后 base64EncodeStr:" + base64EncodeStr);
base64EncodeStr= URLEncoder.encode(base64EncodeStr,"UTF-8");
return base64EncodeStr;
}
/**
* @param content base64处理过的字符串
* @param pkey 密匙
* @return String 返回类型
* @throws Exception
* @throws
* @Title: aesDecodeStr
* @Description: 解密 失败将返回NULL
*/
public static String aesDecodeStr(String content, String pkey) throws Exception {
try {
content=URLDecoder.decode(content,"UTF-8");
System.out.println("待解密内容:" + content);
byte[] base64DecodeStr = Base64.decodeBase64(content);
System.out.println("base64DecodeStr:" + Arrays.toString(base64DecodeStr));
byte[] aesDecode = aesDecode(base64DecodeStr, pkey);
System.out.println("aesDecode:" + Arrays.toString(aesDecode));
if (aesDecode == null) {
return null;
}
String result;
result = new String(Base64.decodeBase64(aesDecode), "UTF-8");
System.out.println("aesDecode result:" + result);
return result;
} catch (Exception e) {
System.out.println("Exception:" + e.getMessage());
throw new Exception("解密异常");
}
}
3.非对称加密
非对称加密算法需要两个密钥来进行加密和解密,分别是公钥和私钥,需要注意的一点,这个公钥和私钥必须是一对的,如果用公钥对数据进行加密,那么只有使用对应的私钥才能解密,反之亦然。非对称加密算法的出现,就是为了解决只有一把密钥的加解密,只要这一把密钥丢失或者被公开,那么加密数据就很容易被攻击。同时,也正是由于非对称加密算法的出现,才有了后面的数字签名、数字证书等等。
- 特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。
- 使用:非对称加密主要有两种使用方面:加解密和数字签名验证。
RSA
- 优点:
密码分配简单,安全保障性高 - 缺点:
1.速度慢,RSA最快的情况也比DES慢上好几倍,RSA的速度比对应同样安全级别的对称密码算法要慢1000倍左右
2.一般来说只用于少量数据加密
3.产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。实际上,这些缺点是非对称加密本身的局限。
/**
* RSA公钥加密
*
* @param str
* 加密字符串
* @param publicKey
* 公钥
* @return 密文
* @throws Exception
* 加密过程中的异常信息
*/
public static String encrypt( String str, String publicKey,KeyPair keyPair) throws Exception{
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
//RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
*
* @param str
* 加密字符串
* @param privateKey
* 私钥
* @return 铭文
* @throws Exception
* 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey,KeyPair keyPair) throws Exception{
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
总结
以上是对工作中遇到的加解密算法的简单介绍,其实还有更为复杂和安全的加解密技术没有介绍例如ECC算法,HMAC等等,如果你对我介绍的算法有了一定的了解,这些更为复杂的算法我们用到的时候很自然的查阅资料就能了解到。不同算法有着不同的特点特性,开发者在选择使用时需要结合考虑使用场景、需求、成本等各个方面的因素。总之我们要对我们的接口数据负责,为了数据安全负责,再见,下回见。