数组转字符串 Arrays.toString(bytes) 将 byte
类型数组(byte[]
)转换成一个字符串表示形式
Hex编码 hex编码是一种用16个字符表示任意二进制数据的方法,是一种编码,而不是加密
1 2 3 用0-9 a-f 16个字符表示 每个十六进制字符代表4bit,也就是每2个十六进制字符代表一个字节
java实现 1 2 3 4 5 6 7 8 public static void main (String[] args) { String name = "你好" ; byte [] bytes = name.getBytes(StandardCharsets.UTF_8); System.out.println(bytes.length); String encode = HexBin.encode(bytes); System.out.println(encode); }
HexBin.encode源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static public String encode(byte[] binaryData) { if (binaryData == null) return null; int lengthData = binaryData.length; int lengthEncode = lengthData * 2; char[] encodedData = new char[lengthEncode]; int temp; for (int i = 0; i < lengthData; i++) { temp = binaryData[i]; if (temp < 0) temp += 256; encodedData[i*2] = lookUpHexAlphabet[temp >> 4]; encodedData[i*2+1] = lookUpHexAlphabet[temp & 0xf]; } return new String(encodedData); }
可以看到lookUpHexAlphabet
就是个码表,如果把这段代码抠出来,然后换一个自定义的码表也行‘
Android实现 首先在模块的build.gradle文件中引入依赖
1 2 3 dependencies { implementation 'com.squareup.okhttp3:okhttp:4.10.0' }
代码中
1 2 3 4 5 6 7 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("aaaaa","test"); ByteString byteString = ByteString.of("你好".getBytes()); System.out.println(byteString.hex()); }
Base64编码 是一种用64个字符表示任意二进制数据的方法
每个base64编码之后的数据中的一位等于原始数据中的6bit
Android实现 1 2 3 4 5 6 7 8 9 10 11 12 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.d("aaaaa","test"); ByteString byteString = ByteString.of("你好".getBytes()); System.out.println(byteString.base64()); // 方法2 Base64.getEncoder().encodeToString("你好".getBytes()); // 方法3 android.util.Base64.encodeToString("你好".getBytes(),0); }
可以hook对应的方法,打印堆栈,来定位关键函数
消息摘要算法 算法特点
1 2 3 4 5 6 7 消息摘要算法、单向散列函数、哈希函数 不同长度输入,产生固定长度输出 散列后的密文不可逆且结果唯一 一般用于校验数据完整性,签名算法一般会把源数据和签名后的值一起提交到服务端
常见算法
MD5,sha1,sha256,sha512…
MD5 Java实现 1 2 3 MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update("test".getBytes()); byte[] digest = md5.digest();
加密后的字节数据可以编码成hex或者base64
碰到加salt的md5的话,可以输入空的值,然后把加密后的结果丢去查
Android实现 1 2 3 MessageDigest md5 = MessageDigest.getInstance("MD5"); // 这里的digest是重载方法 byte[] digest = md5.digest("test".getBytes());
SHA算法 Java实现 MessageDigest md5 = MessageDigest.getInstance("SHA-1");
md5.update("test".getBytes());
byte[] digest = md5.digest();
MAC算法 与MD5和SHA的区别是多个一个密钥,密钥可以随机给
Java实现 1 2 3 4 5 SecretKeySpec secretKeySpec = new SecretKeySpec("a12346".getBytes(),"HmacSHA1"); Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm()); mac.init(secretKeySpec); mac.update("test".getBytes()); mac.doFinal();
对称加密算法 可逆、加解密密钥一样、密钥有位数要求
RC4、DES、3DES、AES
DES算法 Android实现
ECB模式
1 2 3 4 5 6 7 8 9 10 11 // getInstance不指定模式的话,默认是DES/ECB/PKCS5Padding SecretKeySpec secretKeySpec = new SecretKeySpec("a12346".getBytes(),"DES"); Cipher des = Cipher.getInstance("DES"); des.init(Cipher.ENCRYPT_MODE, secretKeySpec); des.doFinal("test".getBytes()); //解密 SecretKeySpec secretKeySpec = new SecretKeySpec("a12346".getBytes(),"DES"); Cipher des = Cipher.getInstance("DES"); des.init(Cipher.DECRYPT_MODE, secretKeySpec); des.doFinal(cipherTextBytes);
CBC模式,需要iv向量
1 2 3 4 5 SecretKeySpec desKey = new SecretKeySpec("12345678".getBytes(),"DES"); IvParameterSpec ivParameterSpec = new IvParameterSpec("12345678".getBytes()); Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding"); des.init(Cipher.ENCRYPT_MODE, desKey, ivParameterSpec); des.doFinal("test".getBytes());
DES加密是每8个字节一组,不足8的倍数就填充,不同的填充方式填充的数据不同
ECB模式是每8个分组,对每组进行加密,得到每组加密之后的结果,不同组之间不影响
CBC模式有iv向量,iv会和第一个分组进行异或,然后再加密得到cipher1,然后cipher1再和第二个分组进行异或得到cipher2…以此类推
注意密钥,iv这些传入的时候实际上都是字节数组,不一定要用字符串.getBytes()方法去传参
1 2 byte[] desKeyBytes = new byte[]{1,2,3,4,5,6,7,8}; SecretKeySpec desKey = new SecretKeySpec(desKeyBytes,"DES");
3DES算法 又叫DESede算法
1 2 3 4 SecretKeySpec secretKeySpec = new SecretKeySpec("123456781234567812345678".getBytes(),"DESede"); Cipher des = Cipher.getInstance("DESede"); des.init(Cipher.ENCRYPT_MODE, secretKeySpec); des.doFinal("test".getBytes());
AES算法 1 2 3 4 5 SecretKeySpec secretKeySpec = new SecretKeySpec("1234567890abcdef".getBytes(),"AES"); AlgorithParameterSpec iv = new AlgorithParameterSpec("1234567890abcdef".getBytes()); Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding"); aes.init(1, key, iv); aes.doFinal("test".getBytes());
填充方式 对称加密算法中,如果使用NoPadding,加密的明文长度必须等于分组长度的倍数,否则报错
如果使用PKCS5Padding,填充1-1个分组长度范围内的字节数
非对称加密算法 典型就是RSA
需要生产一个密钥对,包含公钥和私钥,公钥加密私钥解密,或者私钥加密公钥解密
单次加密长度有限制,公钥无法推导出私钥
密钥格式有PKCS1和PKCS8(这是私钥格式,Java里是PKCS8格式)
RSA_base64 私钥的格式
pkcs1格式开头 BEGIN RSA PRIVATE KEY
pkcs8格式开头 BEGIN PRIVATE KEY
RSA密钥的解析(这里指的是密钥是base64格式的)
1 2 3 4 5 6 7 8 9 byte[] keyBytes = Base64Decoder.decodeBuffer(key); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = KeyFactory.generatePublic(keySpec); byte[] keyBytes = Base64Decoder.decodeBuffer(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey privateKey = KeyFactory.generatePrivate(keySpec);
RSA加解密
1 2 3 4 5 6 7 8 9 Cipher cipher = Cipher.getInstance("RSA/None/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] bt_encrypt = cipher.doFinal(plainText.getBytes()); Cipher cipher = Cipher.getInstance("RSA/None/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); // 这里的encText也是byte数组,如果不是要转 byte[] bt_original = cipher.doFinal(encText);
RSA模式和填充细节
None模式与ECB模式一样
NoPadding,明文最多字节数为密钥字节数,密文与密钥等长,填充字节0,每次加密后的密文不变
pkcs1Padding,明文最大字节数为密钥字节数-11(因为要填充),密文与密钥等长,每次填充的不一样,每次加密后结果不一样
RSA密钥的转换 算法还原的时候可能要用其他的语言来实现算法
有可能手头只有PEM/base64格式的密钥,没有Hex格式的密钥,这个时候就需要完成PEM和hex格式密钥之间的转换
https://cnblogs.com/wyzhou/p/9738964.html
1 openssl rsa -in input.pem -text
会得到modulus,publicExponent,privateExponent,有这三个就可以实现加解密了(加密只需要modulus,publicExponent,这两个在公钥里,解密的话需要privateExponent),这里默认得到的都是十六进制的数据
有少数的modulus,publicExponent,privateExponent使用十进制表示
RSA_Hex
Hex格式下的RSA加解密实现
1 2 3 4 BigInteger N = new BigInteger(stringN, 16); BigInteger E = new BigInteger(stringE, 16); RSAPublicKeySpec keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = KeyFactory.generatePublic(spec);
PS:Java 的 Cipher
在 NoPadding 模式下会自动在明文前填充 0 字节
常见加密算法的结合套路 随机生成AES密钥A,A密钥用于AES加密数据,得到数据密文B,使用RSA对A密钥加密,得到密文C,提交密钥密文C和数据密文B给服务器
数字签名算法 先对数据进行消息摘要,然后对摘要结果进行RSA加密
签名
1 2 3 4 5 PrivateKey priK = getPrivateKey(str_priK); Signature sig = Signature.getInstance("SHA256withRSA"); sig.initSign(priK); sig.update(data.getBytes()); sig.sign();
验证
1 2 3 4 5 PublicKey priK = getPublicKey(str_pubK); Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(pubK); sig.update(data.getBytes()); sig.verify(sign);
CryptoJS 为什么选择使用js来复现算法,因为js实现的算法,可以很方便被任何语言调用
CryptoJS中消息摘要算法的使用 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 26 CryptoJS .MD5 (message);CryptoJS .HmacMD5 (message, key);CryptoJS .SHA1 (message, key);CryptoJS .HmacSHA1 (message, key);CryptoJS .SHA256 (message);CryptoJS .HmacSHA256 (message, key);CryptoJS .SHA512 (message);CryptoJS .HmacSHA512 (message, key);CryptoJS .SHA3 ("testtestesss" , {outputLength : 256 });CryptoJS .MD5 (message + '' );CryptoJS .MD5 (message).toString ();var hasher = CryptoJS .algo .MD5 .create ();hasher.reset (); hasher.update ("ttttttt" ); var hash = hasher.finalize ();console .log (hash + '' );var hmacHasher = CryptoJS .algo .HMAC .create (CryptoJS .algo .SHA256 , "12345678" );hmacHasher.reset (); hmacHasher.update ('message' ); var hmac = hmacHasher.finalize ();
字符串转换 因为update方法除了传字符串还能传入wordArray
wordArray就是直接运行CryptoJS.MD5(message);
得到的输出
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 26 27 string转wordArray CryptoJS .enc .Utf8 .parse (utf8String);CryptoJS .enc .Hex .parse (hexString);CryptoJS .enc .Base64 .parse (base64String);例如 var md5Str = "test" ;CryptoJS .MD5 (md5Str).toString ();var md5Str = "1df10923e0908cffe" ;var HexBytes = CryptoJS .enc .Hex .parse (md5Str);CryptoJS .MD5 (HexBytes ).toString ();wordArray转string wordArray + '' ; wordArray.toString (CryptoJS .enc .Utf8 ); wordArray.toString (CryptoJS .format .Hex ); wordArray.toString (CryptoJS .format .Base64 ); wordArray.toString (); CryptoJS .enc .Utf8 .stringify (wordArray);CryptoJS .enc .Hex .stringify (wordArray);CryptoJS .enc .Base64 .stringify (wordArray);
对称加密算法
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 26 27 28 29 30 31 32 33 34 35 36 37 38 var ciphertext = CryptoJS .DES .encrypt (message, key, cfg);var plaintext = CryptoJS .DES .decrypt (ciphertext, key, cfg);var ciphertext = CryptoJS .TripleDES .encrypt (message, key, cfg);var plaintext = CryptoJS .TripleDES .decrypt (ciphertext, key, cfg);var ciphertext = CryptoJS .AES .encrypt (message, key, cfg);var plaintext = CryptoJS .AES .decrypt (ciphertext, key, cfg);var ciphertext = CryptoJS .RC4 .encrypt (message, key, cfg);var plaintext = CryptoJS .RC4 .decrypt (ciphertext, key, cfg);cfg的格式 var cfg = { iv : iv, mode : CryptoJS .mode .CBC , padding : CryptoJS .pad .Pkcs7 , format : CryptoJS .format .Hex }; var CryptoJS = module .exports ;var plainTextBytes = CryptoJS .enc .Utf8 .parse ("xiaojianbang" );var hexKeyBytes = CryptoJS .enc .Hex .parse ("0102030405060708" );var Utf8IvBytes = CryptoJS .enc .Utf8 .parse ("12345678" );var cfg = { iv : Utf8IvBytes , mode : CryptoJS .mode .CBC , padding : CryptoJS .pad .Pkcs7 }; var cipherTextObj = CryptoJS .DES .encrypt (plainTextBytes, hexKeyBytes, cfg);console .log (cipherTextObj.toString ());console .log (cipherTextObj.toString (CryptoJS .format .Hex ));
对称加密算法补充 cfg中没有传mode和padding,默认使用CBC加密模式,PKCS7填充
加密结果是wordArray对象,调用toString默认转为Base64编码密文,转hex可以使用(因为wordArray对象有ciphertext属性)
1 var hexString = wordArray.ciphertext .toString ()
如果不想手动调用cipherTextObj.toString(CryptoJS.format.Hex)也行,在cfg中手动指定输出格式,不指定就是base64默认
1 format : CryptoJS .format .Hex
这里的format其实就是一个对象,当调用.toString()的时候会调用format对象中的stringify方法,然后这个方法返回toString的字符串,所以可以自定义任何的返回格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 format : { stringify : function (data ){ var e = { ct : data.ciphertext .toString (), miaoshu : "这是自定义的输出" }; return JSON .stringify (e); }, } 那么调用 var cipherTextObj = CryptoJS .DES .encrypt (plainTextBytes, hexKeyBytes, cfg);var ciphertext = ciphertextObj.toString ();就会返回 {"ct" :"xxxxxxx" ,"miaoshu" :"xxxxxxx" }
同时,再解密的时候也会调用format里的parse方法,在parse方法里就需要处理json数据,提取出里面的加密数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 format : { stringify : function (data ){ var e = { ct : data.ciphertext .toString (), miaoshu : "这是自定义的输出" }; return JSON .stringify (e); }, parse : function (data ){ let json = JSON .parse (data); let newVar = CryptoJS .lib .CipherParams .create ({ciphertext : CryptoJS .enc .Hex .parse (json.ct )}); return newVar; } }
RSA加密算法的JS实现 1 2 3 4 5 6 7 8 9 10 11 var JSEncrypt = JSEncryptExports.JSEncrypt; function getEncrtpt(plaintext, publickey){ let jsEncrypt = new JSEncrypt(); jsEncrypt.setPublicKey(publickey); let encrypt = jsEncrypt.encrypt(plaintext); return encrypt; } var publicKeyBase64 = "xxx"; console.log(getEncrtpt("password",publicKeyBase64));
补js环境的话(因为有的js文件写出来是为了在浏览器运行的,node的webstorm下会报错),有时候要把变量定义为{},有时候是window,有时候是this,有时候是global
1 2 var navigator = {}; var window = global;
给jsencrypt加密库添加nopadding填充,无非就是在明文前面添加字节0
JS数字签名算法的使用 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 var signData = "xiaojianbang"; //PKCS1格式的密钥 前缀 -----BEGIN RSA PRIVATE KEY----- 后缀 -----END RSA PRIVATE KEY----- //PKCS8格式的密钥 前缀 -----BEGIN PRIVATE KEY----- 后缀 -----END PRIVATE KEY----- var privateKeyBase64 = "-----BEGIN PRIVATE KEY-----MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAPFFAfEv/zFnURo2\n" + "ZAEZmekyIJjuBHOiDqcON8ElpzK0SJUmclG6rVX8P4kppcPB62wKdJRrPrksIUdT\n" + "T05IRh57mgKIjSqXbUQDfTpz3WhP99Ck+eBAZkctS0M5R0lWUAqeJwK+ZHbg2rI0\n" + "oW5jwvRycHXNxrHvdF/4K1/+XEA7AgMBAAECgYEAsGkDrYWps0bW7zKb1o4Qkojb\n" + "etZ2HNJ+ojlsHObaJOHbPGs7JXU4bmmdTz5LfSIacAoJCciMuTqCLrPEhfmkghPq\n" + "U2MjyjfqYdXALoP7l/vt6QmjY/g1IAsaZN9nFhyjJ2WzgOx1f7gZj4NBSvTdSj7H\n" + "m5E24zkm+p7Qw1z6/mkCQQD7WSXAXcv2v3Vo6qi1FUlkzQgCQLFYqXNSOSPpno3y\n" + "oohUFIkMj0bYGbVE1LzV30Rb6Z8e8yQAByw6l8RuGb2PAkEA9bwb2euyOe6CcqpE\n" + "PNFc+7UlOJAy5epVFKHbu0aNivVpU0hsphqjIGXJGHYTspyEOLqtzILqKPZr6pru\n" + "WvJUlQJBAJoImQUZtlyCGs7wN/G5mN/ocscGpGikd+Lk16hdHbqbdpaoexCyYYUf\n" + "xCHpicw75mW5d2V9Ngu6WZWS2rNqnOsCQCoMK//X8sEy7KNOOyrk8DIpxtqs4eix\n" + "dil3oK+k3OdgIsubYuvxNuR+RjCnU6uGWKGUX9TUudiUgda89/gb6xkCQFm8gD6n\n" + "AyN+PPPKRq2M84+cAbnvjdIAY3OFHfkaoWCtEj5DR0UDuVv7jN7+re2D7id/GkAe\n" + "FAmhvYQwwLnifrw=-----END PRIVATE KEY-----"; function doSign() { var signature = KEYUTIL.getKey(privateKeyBase64); var hSig = signature.signString(signData, "sha256"); return hex2b64(hSig); } console.log(doSign());