科技快速发展,普遍的数据传递成为人与人、人与物、物与物的日常。
高效、快速、安全的数据传递成了数据交流的基石。
为了确保数据的安全性,保护用户的隐私,出现了大量的加密算法。
今天对几种常见的加密算法浅显的记录,并且对AES算法三端统一加密问题给出解决方法。
1.明文:加密前的信息
2.密文:加密后的信息
3.算法:加密或解密算法
4.密钥:算法使用的钥匙
5.对称加密算法:加密算法和解密算法相对称
6.非对称加密算法:加密算法和解密算法不对称
对于这些概念,大家都不会陌生,但是需要自己去实现,我们需要做哪些思考呢?
举一个小例子让大家更清晰的理解这些概念: 将 123456 的每位数字加 1 后得到的是 234567 其中明文就是 123456,密文就是 234567,算法就是给每位数字加 (+1) ,密钥就是 1 这样就很清晰了,那么可以看出,最核心的部分就是算法部分 因为加密方式的算法不同,从而诞生了各种各样的加密方式。 而根据加密算法和解密算法的对称性,产生了对称算法和非对称算法的概念 继续使用上面的例子展现: 123456 ---> (+1) ---> 234567 的加密秘钥为 1 ,加密算法为每位 (+1) 234567 ---> ( -1) ---> 123456 的解密秘钥为 1 ,解密算法为每位 ( -1) 其中加密算法 (+1) 和解密算法 ( -1) 相对称,这种加密算法就称作对称加密 同样,如果加密算法和解密算法非对称,就称为非对称加密 复制代码
严格来说Base64不能算是一种加密方式,更确切的来说是一种编码方式。
但是对于别的算法Base64有着不可忽视的重要性。
计算机中的数据都是二进制的,不管是字符串还是文件,加密后的数据也是二级制的。
很多算法加密后输出的都是byte[],而我们需要的往往是字符串,所以需要需要使用Base64对其进行编码。
严格来说也不是加密算法,中文名为 消息摘要算法第五版 ,是一种哈希算法,是计算机广泛使用的 杂凑算法 之一。
其最重要的性质就是 不可逆、无冲突
所谓的不可逆就是:当你知道x的HASH值,无法求出x
所谓的无冲突就是:但你知道x,无法求出一个y,使x与y的值相同
这两条性质在数学上都是不成立的。
因为一个函数必然可逆,且由于HASH函数的值域有限,理论上会有无穷多个不同的原始值,它们的HASH值都相同。
MD5做到的是求逆和求冲突在计算上的不可能,也就是正向计算很容易,而反向计算即使穷尽人类所有的计算资源都做不到
因为HASH的散列性,其在计算的过程中散列了一些信息,所以无法逆向求解,但是有人说网上有破解的方法。
网上的方法可以说是一种对MD5建立的字典查询,而不是真正意义的算法破解,我也有MD5破解脚本,需要的可以q我。
想深入研究的可以百度 彩虹表
举个简单的例子来理解HASH算法的不可逆性,比如每个人都有指纹,虹膜等,警察叔叔可以将大家的指纹,虹膜都录入一个库。
查询的时候就可以匹配出对应的信息。但是如果没有录入库中,可以根据指纹来推断这个人的长相,身体特征吗?
如果HASH算法可逆,那么数据压缩技术会得到里程碑式的发展
你爱的苍老师大片被用HASH算法压缩成一个长度为128bit的大整数,那还需要种子干吗啊?
特性:① 压缩性:任意长度的数据,算出的MD5值长度都是固定的
② 容易计算:用原数据计算出MD5很容易
③ 抗修改性:对原数据进行改动,哪怕只修改一个字节,所得的MD5值有很大差别。
④ 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(伪造数据)非常困难
(除了MD5外,比较有名的HASH还有sha-1、RIPEMD、Haval等)
① 优点:算法公开、计算量小、加密速度快、加密效率高、可逆
② 缺点:双方使用相同秘钥,安全性降低。对称密码体中只有一种密钥,并且是非公开的,如果要解密就得对方知道密钥,所以保证其安全性就是保证密钥的安全。
③ 常见算法:AES、DES、3DES、RC2、RC4、RC5、IDEA、TDEA、Blowfish、SKIPJACK等
① 特点:
非对称加密有两个密钥: 公开密钥 (公钥publickey)和 私有密钥 (私钥privatekey)
公开密钥和私有密钥是 一对 ,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;
如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。
算法强度复杂、安全依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快
非对称密钥体制有两种密钥,其中一个是公开的,这样就可以不需要像对称密码那样传输对方的密钥了。
非对称加密算法一般效率差,对大型数据加密时间很长,一般用于小数据。
② 常见算法:RSA、Rabin、Elgamal、D - H、ECC(椭圆曲线加密算法)、背包算法等。
AES是一种被广泛使用的加密算法,由于其 快速、高效、安全 的特点,被许多人所青睐。
然而由于前后端开发使用的语言同,经常导致前端加密后而后端无法解密,或者各端对相同的明文加出来的密文不同。
然而无论什么语言,AES的算法总是相同的,因此导致结果不同的原因是加密设置的参数不一致。
因此我们需要 统一 的参数有以下几个:
① 密钥长度:Key Size
② 加密模式:Cipher Mode
③ 填充方式:Padding
④ 初始向量:Initialization Vector
AES算法下,key的长度有三种:128、192、256(bits)。
JDK默认只支持不大于128bits 的密钥,而128bits已能够满足商用需求,在此使用126bits长度。
实现:key为16位128bits,自己定义,保证各端一致
private static String key = "128bitslength*@#";
NSString *key = @"128bitslength*@#";
AES属于块加密(Block Cipher),块加密中有CBC、ECB、CTR、OFB、CFB等几种工作模式。
要保证加出的密文一致,前后端必须使用同样的加密模式,此处使用CBC模式来实现。
由于 块加密只能对特定长度数据块进行加密 ,因此CBC、ECB模式需要在最后一数据块加密前进行数据填充。
CFB、OFB、CTR模式由于与key进行加密操作的是上一块加密后的密文,因此不需要对最后一段明文进行填充。
在 IOS SDK中提供了PKCS7Padding,而JDK则提供了PKCS5Padding。
原则上PKCS5Padding限制了填充的Block Size为8 bytes。
而Java实际上当块大于该值时,其PKCS5Padding与PKCS7Padding是相等的:每需要填充X个字节,填充的值就是X。
使用 除ECB 以外的其他加密模式均需要传入一个初始向量,其大小与Block Size相等(AES的Block Size默认为128bits)
两个平台的API均指出当不传入初始向量时,系统将默认使用一个 全0的初始向量 。
AESCipher.java import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Base64.Decoder; import java.util.Base64.Encoder; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; public class AESCipher { private static String key = "128bitslength*@#"; private static final String IV_STRING = "A-16-Byte-String"; private static final String charset = "UTF-8"; public static String aesEncryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { byte[] contentBytes = content.getBytes(charset); byte[] keyBytes = key.getBytes(charset); byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes); Encoder encoder = Base64.getEncoder(); return encoder.encodeToString(encryptedBytes); } public static String aesDecryptString(String content, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { Decoder decoder = Base64.getDecoder(); byte[] encryptedBytes = decoder.decode(content); byte[] keyBytes = key.getBytes(charset); byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes); return new String(decryptedBytes, charset); } public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE); } public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException { return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE); } private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); byte[] initParam = IV_STRING.getBytes(charset); IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(mode, secretKey, ivParameterSpec); return cipher.doFinal(contentBytes); } public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { String pdaes = AESCipher.aesEncryptString("wylq2018",key); System.out.println(pdaes); String pd = AESCipher.aesDecryptString(pdaes,key); System.out.println(pd); } } 复制代码
AESCipher.h #import <Foundation/Foundation.h> NSString * aesEncryptString(NSString *content, NSString *key); NSString * aesDecryptString(NSString *content, NSString *key); NSData * aesEncryptData(NSData *data, NSData *key); NSData * aesDecryptData(NSData *data, NSData *key); AESCipher.m #import "AESCipher.h" #import <CommonCrypto/CommonCryptor.h> NSString const *kInitVector = @"A-16-Byte-String"; size_t const kKeySize = kCCKeySizeAES128; NSData * cipherOperation(NSData *contentData, NSData *keyData, CCOperation operation) { NSUInteger dataLength = contentData.length; void const *initVectorBytes = [kInitVector dataUsingEncoding:NSUTF8StringEncoding].bytes; void const *contentBytes = contentData.bytes; void const *keyBytes = keyData.bytes; size_t operationSize = dataLength + kCCBlockSizeAES128; void *operationBytes = malloc(operationSize); if (operationBytes == NULL) { return nil; } size_t actualOutSize = 0; CCCryptorStatus cryptStatus = CCCrypt(operation, kCCAlgorithmAES, kCCOptionPKCS7Padding, keyBytes, kKeySize, initVectorBytes, contentBytes, dataLength, operationBytes, operationSize, &actualOutSize); if (cryptStatus == kCCSuccess) { return [NSData dataWithBytesNoCopy:operationBytes length:actualOutSize]; } free(operationBytes); operationBytes = NULL; return nil; } NSString * aesEncryptString(NSString *content, NSString *key) { NSCParameterAssert(content); NSCParameterAssert(key); NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding]; NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding]; NSData *encrptedData = aesEncryptData(contentData, keyData); return [encrptedData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; } NSString * aesDecryptString(NSString *content, NSString *key) { NSCParameterAssert(content); NSCParameterAssert(key); NSData *contentData = [[NSData alloc] initWithBase64EncodedString:content options:NSDataBase64DecodingIgnoreUnknownCharacters]; NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding]; NSData *decryptedData = aesDecryptData(contentData, keyData); return [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]; } NSData * aesEncryptData(NSData *contentData, NSData *keyData) { NSCParameterAssert(contentData); NSCParameterAssert(keyData); NSString *hint = [NSString stringWithFormat:@"The key size of AES-%lu should be %lu bytes!", kKeySize * 8, kKeySize]; NSCAssert(keyData.length == kKeySize, hint); return cipherOperation(contentData, keyData, kCCEncrypt); } NSData * aesDecryptData(NSData *contentData, NSData *keyData) { NSCParameterAssert(contentData); NSCParameterAssert(keyData); NSString *hint = [NSString stringWithFormat:@"The key size of AES-%lu should be %lu bytes!", kKeySize * 8, kKeySize]; NSCAssert(keyData.length == kKeySize, hint); return cipherOperation(contentData, keyData, kCCDecrypt); } ViewController.m #import "ViewController.h" #import "AESCipher.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSString *plainText = @"wy123456"; NSString *key = @"128bitslength*@#"; NSString *cipherText = aesEncryptString(plainText, key); NSLog(@"%@", cipherText); NSString *decryptedText = aesDecryptString(cipherText, key); NSLog(@"%@", decryptedText); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end 复制代码
Android端可以使用Java的端的代码,但是因为 java.util.Base64 包的 aesEncryptBytes()方法 和 getEncoder()方法 需要 API26 以上才可以使用,所以需要进行一些更改,创建Base64Util来进行Base64编码。
具体实现为:
package com.moie.wy.lib.utils.aes; import com.tzcm.factory.chumeifactory.utils.LogUtils; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class AESUtils { private static String key = "128bitslength*@#"; private static final String IV_STRING = "A-16-Byte-String"; private static final String charset = "UTF-8"; public static String EnCode(String content) { try { byte[] contentBytes = content.getBytes(charset); byte[] keyBytes = key.getBytes(charset); byte[] encryptedBytes = aesEncryptBytes(contentBytes, keyBytes); return Base64Util.encode(encryptedBytes); } catch (Exception e) { e.printStackTrace(); } return null; } public static String DeCode(String content) { try { byte[] encryptedBytes = Base64Util.decode(content); byte[] keyBytes = key.getBytes(charset); byte[] decryptedBytes = aesDecryptBytes(encryptedBytes, keyBytes); return new String(decryptedBytes, charset); } catch (Exception e) { e.printStackTrace(); } return null; } public static byte[] aesEncryptBytes(byte[] contentBytes, byte[] keyBytes) { try { return cipherOperation(contentBytes, keyBytes, Cipher.ENCRYPT_MODE); } catch (Exception e) { e.printStackTrace(); } return null; } public static byte[] aesDecryptBytes(byte[] contentBytes, byte[] keyBytes) { try { return cipherOperation(contentBytes, keyBytes, Cipher.DECRYPT_MODE); } catch (Exception e) { e.printStackTrace(); } return null; } private static byte[] cipherOperation(byte[] contentBytes, byte[] keyBytes, int mode) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES"); byte[] initParam = IV_STRING.getBytes(charset); IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(mode, secretKey, ivParameterSpec); return cipher.doFinal(contentBytes); } public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { String pdaes = AESUtils.EnCode("wy123456"); LogUtils.error(pdaes); String pd = AESUtils.DeCode(pdaes); LogUtils.error(pd); } } 复制代码
package com.moie.wy.lib.utils.aes; import java.io.ByteArrayOutputStream; /** * @author yangyang_2000 * @version v1.0 * @date 2017/12/20 16:22 */ public class Base64Util { private static final char[] base64EncodeChars = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; private static byte[] base64DecodeChars = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1}; private Base64Util() { } /** * 将字节数组编码为字符串 */ public static String encode(byte[] data) { StringBuffer sb = new StringBuffer(); int len = data.length; int i = 0; int b1, b2, b3; while (i < len) { b1 = data[i++] & 0xff; if (i == len) { sb.append(base64EncodeChars[b1 >>> 2]); sb.append(base64EncodeChars[(b1 & 0x3) << 4]); sb.append("=="); break; } b2 = data[i++] & 0xff; if (i == len) { sb.append(base64EncodeChars[b1 >>> 2]); sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]); sb.append(base64EncodeChars[(b2 & 0x0f) << 2]); sb.append("="); break; } b3 = data[i++] & 0xff; sb.append(base64EncodeChars[b1 >>> 2]); sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]); sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]); sb.append(base64EncodeChars[b3 & 0x3f]); } return sb.toString(); } /** * 将字符串编码为字节数组 */ public static byte[] decode(String str) throws Exception { byte[] data = str.getBytes("GBK"); int len = data.length; ByteArrayOutputStream buf = new ByteArrayOutputStream(len); int i = 0; int b1, b2, b3, b4; while (i < len) { /* b1 */ do { b1 = base64DecodeChars[data[i++]]; } while (i < len && b1 == -1); if (b1 == -1) { break; } /* b2 */ do { b2 = base64DecodeChars[data[i++]]; } while (i < len && b2 == -1); if (b2 == -1) { break; } buf.write((b1 << 2) | ((b2 & 0x30) >>> 4)); /* b3 */ do { b3 = data[i++]; if (b3 == 61) { return buf.toByteArray(); } b3 = base64DecodeChars[b3]; } while (i < len && b3 == -1); if (b3 == -1) { break; } buf.write(((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)); /* b4 */ do { b4 = data[i++]; if (b4 == 61) { return buf.toByteArray(); } b4 = base64DecodeChars[b4]; } while (i < len && b4 == -1); if (b4 == -1) { break; } buf.write(((b3 & 0x03) << 6) | b4); } return buf.toByteArray(); } } 复制代码
Java端:使用工具eclipse
Android端:使用工具Android Studio
IOS端:使用工具Xcode
key:128bitslength*@#
明文:wy123456
三端加密后为密文:c9GjhapX+Equ9Y09YDCVLA==
三端密文一致。
本文参考:
简书WeLKinXie的文章
CSDN作者uikoo9的文章
百度百科Base64,RSA,AES算法
知乎蒋又新对《什么是哈希算法》的回复
知乎《为什么MD5算法不可逆》的回复
长路漫漫,菜不是原罪,堕落才是原罪。
我的CSDN: blog.csdn.net/wuyangyang_…
我的简书: www.jianshu.com/u/20c2f2c35…
我的掘金: juejin.im/user/58009b…
我的GitHub: github.com/wuyang2000
个人网站:www.xiyangkeji.cn
个人app(茜茜)蒲公英连接:www.pgyer.com/KMdT
我的微信公众号:茜洋 (定期推送优质技术文章,欢迎关注)
Android技术交流群:691174792
以上文章均可转载,转载请注明原创。