前言
本文主要讲解常用加密算法,消息摘要,二进制字符变换等的java实现,对于加密算法本身的原理只会做简单的介绍,详细的原理可百度。
相关概念
- 加密
加密是指将可读取的明文作为输入,通过特定的变换操作得到不易读取的输出(通常是二进制序列),目前常用的加密算法包括 对称加密的AES/DES,非对称加密的RSA/DSA/EC,加密很重要的一点就是解密,无论多复杂的数学变换,一定可以通过相应的逆变换得到原始输入,这是的加密行为才有意义。
- hash(哈希变换)
hash值又称散列值或者消息摘要,对输入的字符串或者二进制序列通过一定的变换得到
固定长度的输出
,它是一个不可逆的过程,理解这个不可逆的过程可以从数学中的求余函数理解,例如:11/10 = 1 ... 1余数是1,以除以10
作为变换,余数
1作为输出,不可能通过余数是1得到被除数是11,因为有可能是21、31、41。。。。。。等等,同时和求余类似,hash碰撞指的就是不同的输入可能得到相同的输出。当然对于真正的hash变换,不可能像求余过程如此简单,但是这个不可逆过程的原理是类似的。常用的hash变换有MD5/SHA1/HmacSHA1/HmacMD5....
等,hash变换的目的并不是让输入不可读取,而是让输入不可改变。
- 字节变换
文件通常会分为文本文件和二进制文件,文本文件通过(Unicode/UTF-8/ASCII)编码之后是可以读取的,而二进制文件是不可读的,因为部分数值没有对应的编码。但是在开发过程中,很多时候需要将不可读的二进制数据转成可读的字符串进行传输,因此就有了字节变换操作,常用的字节变换操作有
Base64,UrlEncoder
,还有通过将二进制转成十六进制字符进行变换,在MD5和SHA1变换中常用。字节变换最主要的目的是:将不易读取或者不易传输的数据转成易读取或者易传输的字符串
相关api介绍
java中对于加密的支持api都在
java.security
和javax.crypto
包下,主要用到的类有:
Cipher
主要用于加密行为,如进行AES/DES/RSA等加密行为
- 初始化对象
static Cipher getInstance(String transformation)
transformation
的组成可以概括为algorithm/mode/padding,algorithm
用于指定加密的方式,mode
用于指定特定加密方式的变换模式,padding
是字节填充规则。mode
和padding
可以不写,可用的transformation
组合有:
<code>//括号数值为所需秘钥的长度 AES/CBC/NoPadding (128) AES/CBC/PKCS5Padding (128) AES/ECB/NoPadding (128) AES/ECB/PKCS5Padding (128) DES/CBC/NoPadding (56) DES/CBC/PKCS5Padding (56) DES/ECB/NoPadding (56) DES/ECB/PKCS5Padding (56) DESede/CBC/NoPadding (168) DESede/CBC/PKCS5Padding (168) DESede/ECB/NoPadding (168) DESede/ECB/PKCS5Padding (168) RSA/ECB/PKCS1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048) RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048) </code>
- 初始化参数
void init(int opmode, Key key)
opmode
用于指定该对象是要进行加密还是解密,key
是加密所用的秘钥信息。
- 加密方法
byte[] doFinal(byte[] input)
cipher.doFinal(byte[] input)
等同于cipher.update(byte[] input); cipher.doFinal();
示例代码:
- AES加解密
<code>@org.junit.Test public void testCipherAES() throws Exception { //指定使用AES加密 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); //使用KeyGenerator生成key,参数与获取cipher对象的algorithm必须相同 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); //指定生成的密钥长度为128 keyGenerator.init(128); Key key = keyGenerator.generateKey(); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] bytes = cipher.doFinal("helloworld".getBytes()); System.out.println("AES加密: " + Base64.getEncoder().encodeToString(bytes)); //由于AES加密在CBC模式下是需要有一个初始向量数组byte[] initializeVector , // 而解密的时候也需要同样的初始向量,因此需要使用加密时的参数初始化解密的cipher,否则会出错 byte[] initializeVector = cipher.getIV(); IvParameterSpec ivParameterSpec = new IvParameterSpec(initializeVector); cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec); //上面三步操作可以用此操作代替 cipher.init(Cipher.DECRYPT_MODE, key, cipher.getParameters()); bytes = cipher.doFinal(bytes); System.out.println("AES解密: " + new String(bytes)); } //输出 AES加密: pRy4ZbW7qgZ33iWBJ60BDA== AES加密: helloworld </code>
- DES加解密
<code>@org.junit.Test public void testCipherDES() throws Exception { //指定使用DES加密 Cipher cipher = Cipher.getInstance("DES"); //使用KeyGenerator生成key,参数与获取cipher对象的algorithm必须相同 KeyGenerator keyGenerator = KeyGenerator.getInstance("DES"); //DES的秘钥长度必须是56位 keyGenerator.init(56); Key key = keyGenerator.generateKey(); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] bytes = cipher.doFinal("helloworld".getBytes()); System.out.println("DES加密: " + Base64.getEncoder().encodeToString(bytes)); //与AES不同,由于DES并不需要初始向量,因此解密的时候不需要第三个参数 cipher.init(Cipher.DECRYPT_MODE, key); bytes = cipher.doFinal(bytes); System.out.println("DES解密: " + new String(bytes)); } //输出 DES加密: XoG4lEjZN4VBlZYTXjw6BQ== DES解密: helloworld </code>
- RSA加解密
<code>@org.junit.Test public void testCipherRSA() throws Exception { //获取cipher对象 Cipher cipher = Cipher.getInstance("RSA"); //通过KeyPairGenerator来生成公钥和私钥 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PublicKey publicKey = keyPair.getPublic();//公钥 PrivateKey privateKey = keyPair.getPrivate();//私钥 /*加密*/ cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] bytes = cipher.doFinal(TEXT.getBytes()); final String encryptText = Base64.getEncoder().encodeToString(bytes); System.out.println("RSA公钥加密:" + encryptText); /*解密*/ cipher.init(Cipher.DECRYPT_MODE, privateKey); bytes = cipher.doFinal(Base64.getDecoder().decode(encryptText)); System.out.println("RSA解密:" + new String(bytes)); } //输出 RSA公钥加密:bKbbpARpcHcCqcMdGmA/WzvyO2G3eXFJhmrK5F0yFlJsoGohg4XIq5egNc1eBQwP7BRD6m7c12byB/KpYNgWg7J5Y3kupWBahZyhJ7SWWF0YY9CrdWf55zQ/CPyn+KlWQg1ViBnIBnejABFuqjDgBmZ3Q3txT1tD4MIpGPCE+NY= RSA私钥解密:helloworld </code>
Mac
该类主要用作Hmac运算,初始化方法
Mac.getInstance(String algorithm)
的可用参数有
<code>HmacMD5 HmacSHA1 HmacSHA224 HmacSHA256 HmacSHA384 HmacSHA512 </code>
示例代码
<code>@org.junit.Test public void testHmac() throws Exception { Mac mac = Mac.getInstance("HmacMD5"); //第一个参数可以是任意字符串,第二个参数与获取Mac对象的algorithm相同 SecretKeySpec secretKeySpec = new SecretKeySpec("123456".getBytes(), "HmacMD5"); mac.init(secretKeySpec); byte[] bytes = mac.doFinal("helloworld".getBytes()); System.out.println("HmacMD5结果:" + HexUtil.toHexString(bytes)); mac = Mac.getInstance("HmacSHA1"); mac.init(new SecretKeySpec("123456".getBytes(), "HmacSHA1")); bytes = mac.doFinal("helloworld".getBytes()); System.out.println("HmacSHA1结果:" + HexUtil.toHexString(bytes)); } //输出 HmacMD5结果:2449233556af565ecbb2fd6266df853b HmacSHA1结果:ef9079b9e2e79c67a962f87e2a87af4f35c2ae37 </code>
Signature
signature类用于提供数字签名,用于保证数据的完整性
示例代码,Signature.getInstance(String algorithm)
的可用参数有
<code>NONEwithRSA MD2withRSA MD5withRSA SHA1withRSA SHA224withRSA SHA256withRSA SHA384withRSA SHA512withRSA NONEwithDSA SHA1withDSA SHA224withDSA SHA256withDSA NONEwithECDSA SHA1withECDSA SHA224withECDSA SHA256withECDSA SHA384withECDSA SHA512withECDSA </code>
示例代码:
<code>@org.junit.Test public void testSignature() throws Exception { Signature signature = Signature.getInstance("NONEwithRSA"); //KeyPairGenerator生成公钥和私钥 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PublicKey publicKey = keyPair.getPublic(); PrivateKey privateKey = keyPair.getPrivate(); //用私钥初始化signature signature.initSign(privateKey); //更新原始字符串 signature.update(TEXT.getBytes()); byte[] bytes = signature.sign(); String sign = Base64.getEncoder().encodeToString(bytes); System.out.println("数字签名: " + sign); //用公钥初始化signature signature.initVerify(publicKey); //更新原始字符串 signature.update(TEXT.getBytes()); //校验签名是否正确 boolean result = signature.verify(Base64.getDecoder().decode(sign)); System.out.println("签名校验结果: " + result); } </code>
MessageDigest
MessageDigest
主要是做hash变换(也称消息摘要或者散列值)
示例代码:
<code> @org.junit.Test public void testMessageDigest() throws Exception { //参数可以是 MD5,MD2,MD5,SHA-1,SHA-224,SHA-256,SHA-384,SHA-512 MessageDigest messageDigest = MessageDigest.getInstance("MD5"); byte[] bytes = messageDigest.digest("helloworld".getBytes()); //将二进制数组转成16进制字符串输出 System.out.println("MD5哈希变换:" + HexUtil.toHexString(bytes)); messageDigest = MessageDigest.getInstance("SHA-1"); bytes = messageDigest.digest("helloworld".getBytes()); System.out.println("SHA1哈希变换:" + HexUtil.toHexString(bytes)); } //HexUtil public static String toHexString(byte[] data) { StringBuilder builder = new StringBuilder(); int len = data.length; String hex; for (int i = 0; i < len; i++) { hex = Integer.toHexString(data[i] & 0xFF); if (hex.length() == 1) { builder.append("0"); } builder.append(hex); } return builder.toString(); } //输出 可以通过标准在线工具检验输出结果的准确性 MD5哈希变换:fc5e038d38a57032085441e7fe7010b0 SHA1哈希变换:6adfb183a4a2c94a2f92dab5ade762a47889a5a1 </code>
KeyGenerator
用于生成秘钥,
KeyGenerator.getInstance(String algorithm)
支持的参数有
<code>AES (128) DES (56) DESede (168) HmacSHA1 HmacSHA256 </code>
示例代码
<code>@org.junit.Test public void testKeyGenerator() throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); //初始化方法有多种,根据需要选择 keyGenerator.init(128); // keyGenerator.init(new SecureRandom("1234567".getBytes())); SecretKey key = keyGenerator.generateKey(); //key的二进制编码 将它保存到文件中 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] bytes = cipher.doFinal("helloworld".getBytes()); System.out.println("加密数据: " + Base64.getEncoder().encodeToString(bytes)); /*=========保存key的二进制编码=========*/ byte[] keyBytes = key.getEncoded(); FileOutputStream fos = new FileOutputStream("F://test/key.txt"); fos.write(keyBytes); fos.flush(); fos.close(); /*============从文件中读取编码并恢复key==============*/ FileInputStream fis = new FileInputStream("F://test/key.txt"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len; byte[] buffer = new byte[1024]; while ((len = fis.read(buffer)) > 0) { bos.write(buffer, 0, len); } fis.close(); /*==============使用SecretKeySpec重新生成key============*/ SecretKeySpec secretKeySpec = new SecretKeySpec(bos.toByteArray(), "AES"); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, cipher.getParameters().getParameterSpec(IvParameterSpec.class)); bytes = cipher.doFinal(bytes); System.out.println("解密数据: " + new String(bytes)); } //输出 加密数据: 0vUaeC2VWEvVpUWeDfgGhg== 解密数据: helloworld </code>
KeyPairGenerator
KeyPairGenerator用于生成一对密钥对,用于做非对称加密操作。
KeyPairGenerator.getInstance(String alorithm)
的可用参数为:
<code>DSA RSA EC </code>
代码生成的密钥对通常需要将公钥和私钥保存到文件中,这样才能够持久化进行操作,下面演示两种保存的实现
- 分别保存公钥和私钥的二进制编码
<code>@org.junit.Test public void testSaveKeyPair2() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); PublicKey oldPbk = keyPair.getPublic(); PrivateKey oldPrk = keyPair.getPrivate(); Cipher cipher = Cipher.getInstance("RSA"); /*============使用原始私钥加密,重新生成的公钥解密===============*/ cipher.init(Cipher.ENCRYPT_MODE, oldPrk); byte[] bytes = cipher.doFinal("helloworld".getBytes()); System.out.println("原始私钥加密: " + Base64.getEncoder().encodeToString(bytes)); /*提取公钥的比特编码经过Base64转换后保存到文件,注意公钥的比特编码是X.509格式*/ byte[] pbks = Base64.getEncoder().encode(oldPbk.getEncoded()); File file = new File("F://test/public.key"); FileOutputStream fos = new FileOutputStream(file); fos.write(pbks); fos.flush(); fos.close(); /*从文件中提取公钥比特编码并恢复成公钥*/ file = new File("F://test/public.key"); FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) > 0) { bos.write(buffer, 0, len); } pbks = Base64.getDecoder().decode(bos.toByteArray()); X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(pbks); //重新得到公钥 PublicKey newPbk = KeyFactory.getInstance("RSA").generatePublic(encodedKeySpec); cipher.init(Cipher.DECRYPT_MODE, newPbk); bytes = cipher.doFinal(bytes); System.out.println("新的公钥解密: " + new String(bytes)); /*============使用原始公钥加密,重新生成的私钥解密===============*/ cipher.init(Cipher.ENCRYPT_MODE, oldPbk); bytes = cipher.doFinal("helloworld".getBytes()); System.out.println("原始私钥加密: " + Base64.getEncoder().encodeToString(bytes)); /*省略了文件存取操作,与公钥相同*/ byte[] prks = oldPrk.getEncoded(); /*私钥的比特编码是pkcs8格式*/ PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(prks); PrivateKey newPrk = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec); cipher.init(Cipher.DECRYPT_MODE, newPrk); bytes = cipher.doFinal(bytes); System.out.println("新的私钥解密: " + new String(bytes)); } //输出 原始私钥加密: CO7FU1hQsEd8fYV4ZfWXquo/qrktte2n/WakdHJuw001aa9RM/mYl6yC6jLMGlm0fxuYlH92Zv9jA7k/0TVuor8Csvzmbm00RMBhnQCme+aQQbSoZDZEwJj1HtW6aK5MJRI4l/1W+g5X+Fs/6TLlbXpJM0k4epGMKUWwhO6cUiM= 新的公钥解密: helloworld 原始私钥加密: ixqqoM3aRig3P6GGPICsSOdH8KXRrlFn9GB1OVIWt46Q9ROsS84BW693fKB9ea8CnLJayc2KU1yhPlHHqq08gU8WOxVYeBQ4Bi3MnoJzUluE/UWNaMYZt/jCB6NZx57XEpNJ6uKG5TUmZJm+eoK0BF7A8sOX96UbPuZlHd4lD0w= 新的私钥解密: helloworld </code>
- 保存密钥对的特征值 公钥(N,e)私钥(N,d)
<code> @org.junit.Test public void testSaveKeyPair() throws Exception { final String algorithm = "RSA"; KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm); keyPairGenerator.initialize(1024); KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); /*特征值N e d*/ BigInteger N = publicKey.getModulus(); BigInteger e = publicKey.getPublicExponent(); BigInteger d = privateKey.getPrivateExponent(); /**/ String nStr = Base64.getEncoder().encodeToString(N.toByteArray()); String eStr = Base64.getEncoder().encodeToString(e.toByteArray()); String dStr = Base64.getEncoder().encodeToString(d.toByteArray()); /*将这三个字符串保存到文件或者数据库,通常n,e可以保存在客户端,而n,d的数据必须保存在服务端*/ N = new BigInteger(Base64.getDecoder().decode(nStr)); e = new BigInteger(Base64.getDecoder().decode(eStr)); d = new BigInteger(Base64.getDecoder().decode(dStr)); /*根据N,e生成公钥*/ RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(N, e); PublicKey pbk = KeyFactory.getInstance(algorithm).generatePublic(publicKeySpec); Cipher cipher = Cipher.getInstance(algorithm); cipher.init(Cipher.ENCRYPT_MODE, pbk); //bytes 是加密后的数据 byte[] bytes = cipher.doFinal("helloworld".getBytes()); //用base64转换输出 System.out.println("加密数据:" + Base64.getUrlEncoder().encodeToString(bytes)); /*根据N,d生成私钥*/ RSAPrivateKeySpec ps = new RSAPrivateKeySpec(N, d); PrivateKey prk = KeyFactory.getInstance(algorithm).generatePrivate(ps); cipher.init(Cipher.DECRYPT_MODE, prk); bytes = cipher.doFinal(bytes); System.out.println("解密数据:" + new String(bytes)); } //输出 加密数据:nVqRtqMDvnm-4pjW0R1Q6sRCRbLpK4WRtG342ydEa8069Kv2OVRGE1Rm3iEFZjFCyh_z_0jlf5liqCgDCkN9I2Ci1qWvrvQo9wZKQG5g86OrxWHs7n1Kg_SXR3rNC-55jPxQAYUXpw-U9XX4ls7aQ85pk2BMZLYoRbwo3ktZAxM= 解密数据:helloworld </code>
【说明】:本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:254677821)!