密码学 概要:
在逆向中,非常有必要了解一下密码学,在逆向过程中,有些参数不知道来源,可能是随机生成、标准算法、自写算法加密
在安卓中,标准算法加密通常会 出现在Java、so(C\C++)、JS中
对于标准算法,Java中有现成的API调用,如果要使用这些API就需要使用指定的方法,就有了hook的机会,这些前面已经操作过了
C\C++中没有现成的系统API调用,开发者要么自己去实现算法,要么调用别人写好的模块,算法的运行就不依靠系统API,因此这些方法名可以进行混淆。我们需要根据各种标准算法的特征,去识别是否为标准算法
JS中没有系统API但是有知名的第三方库:CryptoJS、jsencrypt等
常见的算法:
信息摘要算法(散列函数、哈希函数)
MD5、SHA、MAC
对称加密算法
DES、3DES、AES
非对称加密算法
RSA
数字签名算法
MD5withRSA、SHA1withRSA、SHA256withRSA
H5的app逆向 使用前面提到过的查看app界面控件的小东西,看一下
这个页面只有框架,这个就是将一个网页做的像app登录的页面,然后放到这个框架里
那么这个时候的逆向思路是什么呢
1、H5的核心代码通常在JS文件中
远程调试、修改JS代码注入代码
2、WebView远程调试
注意:a. 手机端的WebView版本要比电脑端的chrome版本低
b. 手机端的WebView要开启可调式
c. 需要VPN,因为点击inspect时要下载一些东西
d. 通常app中的WebView是不可调试的,需要hook来开启调试
WebView调试准备 上面有四条了,chrome访问
可以看到是有设备的一些信息的
在手机中尝试随便访问一个网站,然后刷新chrome页面,是可以看到有页面情况的
也是有webView的,而且为啥拿百度呢,因为百度是开启了webview调试的
点击chrome中的inspect就可以进入谷歌的开发者工具,可以打断点,调试什么的,剩下的就交给JS逆向了
再回头来看怎么开启WebView调试
不开启调试是获取不到东西的
hook掉WebView方法,设置为开启调试状态
代码hook了所有的构造方法,在构造方法中设置开启调试,再hook掉开启方法,始终保持为true
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 39 40 41 42 43 44 45 46 47 48 49 50 51 Java .perform (function ( ){ var WebView = Java .use ('android.webkit.WebView' ); WebView .$init .overload ('android.content.Context' ).implementation = function (a ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' ).implementation = function (a, b ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' , 'int' ).implementation = function (a, b, c ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b, c); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' , 'int' , 'boolean' ).implementation = function (a, b, c, d ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b, c, d); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' , 'int' , 'int' ).implementation = function (a, b, c, d ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b, c, d); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' , 'int' , 'java.util.Map' , 'boolean' ).implementation = function (a, b, c, d, e ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b, c, d, e); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .$init .overload ('android.content.Context' , 'android.util.AttributeSet' , 'int' , 'int' , 'java.util.Map' , 'boolean' ).implementation = function (a, b, c, d, e, f ){ console .log ("WebView.$init is called!" ); var retval = this .$init(a, b, c, d, e, f); this .setWebContentsDebuggingEnabled (true ); return retval; } WebView .setWebContentsDebuggingEnabled .implementation = function ( ){ this .setWebContentsDebuggingEnabled (true ); console .log ("setWebContentsDebuggingEnabled is called!" ); } });
将这个脚本重启附加到app中
必须要重启不能附加,因为页面都构建完成了,时机过去了
1 frida -U -f com.zngst.app -l hook_webview.js
这个时候就成功了
登录以下是,抓个包
从这里可以跳转到登录有关的方法,跳转,打断点就不展示了
H5的app也分很多种
1、纯JS发包,这个时候可以在远程调试工具上抓到包,也有相应JS代码
2、部分JS发包,部分Java发包,这个时候有些包可以在调试工具上抓到,有些不行,需要分析
比如:Java和JS的相互调用,从这个角度入手,找Java里面的接口。
3、纯Java发包,典型的就是uni-app,但是uni-app核心代码是在JS中,因为就是用JS写的。这个就牛逼了。发包在Java中,谷歌中看不到代码,这个时候远程调试就没啥用了。只能修改JS代码注入代码。对uni-app感兴趣可以下载一个HBuilderX耍一下
HEX编码 闲话少扯,看密码学内容,H5的app弄到调试的位置剩下的就和JS逆向差不多了
概述:
HEX编码是一种用16进制字符表示任意二进制数据的方法
是一种编码,不存在加密
一个字节的范围是 0-255 ,hex编码用两个16进制字符表示这个一个字节
第一个F就代表了二进制中前四个比特位 1111
。也就是说十六进制中的一个十六进制字符占四个比特位,半个字节。两个十六进制字符,就可以表示八个比特位一个字节的数据。
注意和字符编码区分开来:
字符编码有 ASCII、UTF-8、GBK
字符编码说白了是一种映射规则,将字符映射到唯一一种状态(二进制字符串),这个就是编码。也就是说如果没有字符编码来表示特定二进制的意思,那我们就只能读0101101这样的01字符串了
USC2、URL和HEX差不多是一种编码方式
hex编码的代码实现及码表 这个是一个针对字符串的实现,还有char的
是用函数将字符串转换成二进制,然后转换成16进制的表示方式
char数组直接转换
不够底层,看一下Java第三方库的一个处理方法
看注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public String hex (data) { char [] HEX_DIGITS = {'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' } char [] result = new char [data.length * 2 ]; int c = 0 ; for (byte b: data) { result[c++] = HEX_DIGITS[(b >> 4 ) & 0xf ]; result[c++] = HEX_DIGITS[b & 0xf ]; } return new String (result) }
光看这个hex编码有点无聊,思路打开,为什么码表一定要0-9+a-f呢,将码表改一下,那就是一个非标准的hex编码。这个时候就需要找到这个码表了
hex编码的特点:
1、用0-9 a-f 十六个字符表示
2、每个十六进制字符代表4bit,也就是2个十六进制字符代表一个字节
3、在实际的应用中,比如密钥初始化,一定要分清楚传进去的密钥是哪种编码,采用对应的方式解析,才能得到对应的密钥
4、编程中的很多问题,需要从字节甚至二进制位的角度去考虑
Base64编码
用64个字符表示任意二进制数据的方法
base64是一种编码,而非加密
64个字符表示256种情况。和hex编码不同,hex编码是16*16正好为256,所以正好可以将一个字节拆开,用两个十六进制字符表示。一个十六进制字符占4bit
与hex不同的是base64编码的一个字符占6bit,用四个字符表示三个字节 6*4=8*3
base64使用的字符为 A-Z a-z 0-9 + / =
实际使用的是65个,最后的等号是用来补位填充的,看上面的表示方式应该也能理解为啥需要补位吧
base64的应用比较光,比如RSA密钥、加密后的密文、图片等数据,会有一些不可见的字符,直接转成文本传输的话,会有乱码、数据错误、数据丢失等情况,就需要用到base64编码
瞅一下实现代码
大致了解一下这个处理过程
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 internal fun ByteArray.encodeBase64(map: ByteArray = BASE64): String { val length = (size + 2 ) / 3 * 4 val out = ByteArray(length) var index = 0 val end = size - size % 3 var i = 0 while (i < end) { val b0 = this [i++].toInt() val b1 = this [i++].toInt() val b2 = this [i++].toInt() out[index++] = map[(b0 and 0xff shr 2 )] out[index++] = map[(b0 and 0x03 shl 4 ) or (b1 and 0xff shr 4 )] out[index++] = map[(b1 and 0x0f shl 2 ) or (b2 and 0xff shr 6 )] out[index++] = map[(b2 and 0x3f )] } when (size - end) { 1 -> { val b0 = this [i].toInt() out[index++] = map[b0 and 0xff shr 2 ] out[index++] = map[b0 and 0x03 shl 4 ] out[index++] = '=' .code.toByte() out[index] = '=' .code.toByte() } 2 -> { val b0 = this [i++].toInt() val b1 = this [i].toInt() out[index++] = map[(b0 and 0xff shr 2 )] out[index++] = map[(b0 and 0x03 shl 4 ) or (b1 and 0xff shr 4 )] out[index++] = map[(b1 and 0x0f shl 2 )] out[index] = '=' .code.toByte() } } return out.toUtf8String() }
它其实还有一套码表,是BASE64_URL_SAFE
1 2 internal val BASE64_URL_SAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" .encodeUtf8().data
看名字就知道用途了,如果用于url编码,+就变为空格了,数据就出大问题,所以就需要把+/给替换掉,就换成了-_,这样一替换就不需要再进行url编码了
再了解一下核心科技,转换字符部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 val b0 = this [i++].toInt() val b1 = this [i++].toInt() val b2 = this [i++].toInt() out[index++] = map[(b0 and 0xff shr 2 )] out[index++] = map[(b0 and 0x03 shl 4 ) or (b1 and 0xff shr 4 )] out[index++] = map[(b1 and 0x0f shl 2 ) or (b2 and 0xff shr 6 )] out[index++] = map[(b2 and 0x3f )]
base64编码的特点
1、Base64编码是一种编码,编码后会增加字节数
2、算法可逆,解码很方便,不用于私密信息通信
3、标准的Base64每行76个字符,行末添加换行符(这个需要注意,在拿到base64字符串进行解密操作的时候,一定不要忘了加上换行符)
4、加密后的字符串只有65种字符,不可打印的字符也能传输
5、在Java层可以通过hook对应的方法名来快速定位关键代码
6、在so层可以通过输入输出的数据和码表来确定算法
工具函数封装 使用安卓的一个API,ByteString将byte数组转换成对应的hex编码或者base64编码,方便数据的查找和显示
1 2 3 4 5 6 7 8 9 10 11 12 13 Java .perform (function ( ) { var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ) function toBase64 (tag, data ) { console .log (tag + "Base64:" + ByteString .of (data).base64 ()); } function toHex (tag, data ) { console .log (tag + "hex:" + ByteString .of (data).hex ()); } function toUtf8 (tag, data ) { console .log (tag + "Utf8:" + ByteString .of (data).utf8 ()); } })
信息摘要算法
特点:
1、不同长度的输入,产生固定长度的输出
2、信息摘要算法/单向散列函数/哈希函数
3、散列后的密文不可逆
4、散列后的结果唯一
5、哈希碰撞
6、一般用于校验数据完整性、签名sign
由于密文不可逆,所以服务端也无法解密
想要验证,只能跟前端一样再重新验证、计算签名
签名算法一般会把源数据和签名后的值一起提交到数据段
要保证在签名的时候的数据和提交上去的源数据一致
常见算法:MD5、SHA1、SHA256、SHA512、HmacMD5、HmacSHA1、HmacSHA256、HmacSHA512、RIPEMD160、HmacRIPEMD160、PBKDF2、EvpKDF
根据密文不可逆就可以推测出,签名的明文一定在数据包内,让服务端知道,否则服务端无法验证密文真伪
之前在Java层逆向的时候,小记了一下如何区分签名算法
SHA-0已经废弃了,SHA-3虽然2011发布,但是不常见,常见的是MD5、SHA-1、SHA-256,这个256是SHA-2的256,SHA-2的256和SHA-3的256算出来的结果是不一样的
输出散列值长度是输出的结果占的字节数,比如MD5占128个字节
资料区块长度是算法进行分组摘要,如MD5每组分512bit
哈希碰撞:即出现不同的信息算出的MD5相同的情况,所以现在很多都使用SHA-256进行签名,虽然哈希碰撞的概率很小,但是是客观存在的
MD5 MD5的Java实现,想要实现MD5很简单,因为Java已经写好这个底层了,直接拿来主义就好
1 2 3 4 5 6 7 MessageDigest md5 = MessageDigest.getInstance("MD5" );md5.update("demo" .getBytes()); byte [] bytes = md5.digest();
算法加盐 通俗点来说就是在明文前/后加上一段固定的字符串,来计算MD5,想要获取盐值也很简单,传一个空的得到MD5去网站上,爆破一下就好了
https://www.cmd5.com/
SHA SHA的Java实现
和MD5的方法几乎一模一样,就是把传进去的名字改一下
1 2 3 4 5 6 7 MessageDigest sha1 = MessageDigest.getInstance("SHA-1" );sha1.update("demo" .getBytes()); byte [] bytes = sha1.digest();
算法通杀 所谓的通杀也只是hook标准算法需要走的Java层一些函数,如果不走这个函数也杀不掉,如果不是标准算法更杀不掉
MD5的hook 将之前的工具函数和打印堆栈先拿过来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Java .perform (function ( ) { function showStacks ( ) { console .log ( Java .use ("android.util.Log" ) .getStackTraceString ( Java .use ("java.lang.Throwable" ).$new() ) ); } var ByteString = Java .use ("com.android.okhttp.okio.ByteString" ) function toBase64 (tag, data ) { console .log (tag + "Base64:" + ByteString .of (data).base64 ()); } function toHex (tag, data ) { console .log (tag + "hex:" + ByteString .of (data).hex ()); } function toUtf8 (tag, data ) { console .log (tag + "Utf8:" + ByteString .of (data).utf8 ()); } })
如果要hook安卓的md5,根据上方的实现代码,有两个方法需要hook,一个是 update 另一个是 digest,因为 digest 也是可以传明文数值的
update这个方法在 java.security 包下的 MessageDigest 类中,有很多重载方法
hook update方法
因为加密的数据可能不是明文,而是乱码,这个时候就需要之前的工具函数来进行转换显示
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 var messageDigest = Java .use ("java.security.MessageDigest" )messageDigest.update .overload ('byte' ).implementation = function (data ) { console .log ("MessageDigest update('byte'): " + data); showStacks (); return this .update (data); } messageDigest.update .overload ('java.nio.ByteBuffer' ).implementation = function (data ) { console .log ("MessageDigest update('java.nio.ByteBuffer'): " + data); showStacks (); return this .update (data); } messageDigest.update .overload ('[B' ).implementation = function (data ) { console .log ("MessageDigest update('[B')" ); showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " update('[B')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("===========================================" ); return this .update (data); } messageDigest.update .overload ('[B' , 'int' , 'int' ).implementation = function (data,start, len ) { console .log ("MessageDigest update('[B', 'int', 'int')" ); showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " update('[B')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("==============================================" , start, len); return this .update (data, start, len); }
SHA SHA算法在安卓中实现的时候,就和MD5几乎一样,只有一个名字不一样,但是又使用了 MessageDigest 类的 getAlgorithm 方法,来获取传入的名字,这个时候MD5的和SHA的hook就是互通的
hook digest方法
toUtf8就是看热闹,包乱码的
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 39 40 41 42 43 messageDigest.digest .overload ().implementation = function ( ) { console .log ("MessageDigest digest()" ); showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " digest data" ; var result = this .digest (); toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("==================================" ); return result; } messageDigest.digest .overload ('[B' ).implementation = function (data ) { console .log ("MessageDigest digest()" ); showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " digest data" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); var result = this .digest (data); toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("==================================" ); return result; } messageDigest.digest .overload ('[B' , 'int' , 'int' ).implementation = function (data, start, len ) { console .log ("MessageDigest digest()" ); showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " digest data" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); var result = this .digest (data, start, len); toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("==================================" ); return result; }
MAC算法 MAC系列算法
算法
摘要长度
备注
HmacMD5
128
Java6实现
HmacSHA1
160
Java6实现
HmacSHA256
256
Java6实现
HmacSHA384
384
Java6实现
HmacSHA512
512
Java6实现
HmacMD2
128
Bouncy Castle实现
HmacMD4
128
Bouncy Castle实现
HmacSHA224
224
Bouncy Castle实现
MAC算法和MD和SHA算法的唯一区别就是多了一个密钥,密钥可以随便给
Java实现
1 2 3 4 5 6 7 8 9 10 11 SecretKeySpec secretKeySpec = new SecretKeySpec ("a12345678" .getBytes(), "HmacSHA1" ); Mac mac = Mac.getInstance(secretKeySpec.getAlgorithm()); mac.init(secretKeySpec); mac.update("demo" .getBytes()); mac.doFinal();
hook MAC算法 根据Java实现来hook算法
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 var mac = Java .use ("javax.crypto.Mac" )mac.init .overload ('java.security.Key' ).implementation = function (key ) { console .log ("Mac Key" ) showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac Key" ; toBase64 (tag, key.getEncoded ()); toHex (tag, key.getEncoded ()); toUtf8 (tag, key.getEncoded ()); return this .init (key); } mac.init .overload ('java.security.Key' , 'java.security.spec.AlgorithmParameterSpec' ).implementation = function (key ) { console .log ("Mac Key" ) var algorithm = this .getAlgorithm (); var tag = algorithm + "Mac Key" ; toBase64 (tag, key.getEncoded ()); toHex (tag, key.getEncoded ()); toUtf8 (tag, key.getEncoded ()); return this .init (key); } mac.update .overload ('byte' ).implementation = function (data ) { console .log ("Mac update('byte')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac update('byte')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("=====================================" ); return this .update (data); } mac.update .overload ('java.nio.ByteBuffer' ).implementation = function (data ) { console .log ("Mac update('java.nio.ByteBuffer')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac update('java.nio.ByteBuffer')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("=====================================" ); return this .update (data); } mac.update .overload ('[B' ).implementation = function (data ) { console .log ("Mac update('[B')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac update('[B')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("=====================================" ); return this .update (data); } mac.update .overload ('[B' , 'int' , 'int' ).implementation = function (data, start, len ) { console .log ("Mac update('[B', 'int', 'int')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac update('[B', 'int', 'int')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("=====================================" , start, len); return this .update (data, start, len); } mac.doFinal .overload ().implementation = function ( ) { console .log ("Mac doFinal()" ) var result = this .doFinal (); var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac doFinal()" ; toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("======================================" ); return result; } mac.doFinal .overload ('[B' ).implementation = function (data ) { console .log ("Mac doFinal('[B')" ) var result = this .doFinal (data); var algorithm = this .getAlgorithm (); var tag = algorithm + " Mac doFinal()" ; toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("======================================" ); return result; } mac.doFinal .overload ('[B' , 'int' ).implementation = function (data, start ) { console .log ("Mac doFinal('[B', 'int')" ) return this .doFinal (data, start); }
对称加密算法
1、加密/解密过程可逆的算法,叫做加密算法
2、加密/解密使用相同的密钥,叫做对称加密算法
3、对称加密算法的密钥可以随便给,但是有位数要求
4、对称加密算法的输入数据没有长度要求,加密速度快
5、各算法的密钥长度
RC4 密钥长度1-256字节
DES 密钥长度8字节
3DES/DESede/TripleDES 密钥长度24字节
AES 密钥长度16、24、32字节
根据密钥长度不同的AES又分为AES-128、AES-192、AES-256
6、对称加密分类
a. 序列加密/流加密:以字节流的方式,依次加密(解密)明文(密文)中的每一个字节
RC4
b. 分组加密:将明文信息分组(每组有多个字节),逐组进行加密
DES、3DES、AES
PS:MAC密钥是可以无限给的,如果密钥长度超过512bit,就计算密钥的MD5,使用MD5值作为密钥
PS:流加密和分组加密很好区分,流加密每增加一个字符密文长度就会变化,而分组加密是在一定的字节范围内,密文长度是不变的,超出这个长度,密文长度变长一倍
DES算法 Java实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 SecretKeySpec secretKeySpec = new SecretKeySpec ("123454678" .getBytes(), "DES" );DESKeySpec desKeySpec = new DESKeySpec ("12345678" .getBytes());Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding" );des.init(Cipher.ENCRYPT_MODE, secretKeySpec); byte [] result = des.doFinal("demo" .getBytes());String hexStr = ByteString.of(result).hex();System.out.println(hexStr); System.out.println(Base64.getEncoder().encodeToString(result)); des.init(Cipher.DECRYPT_MOOD, secretKeySpec); byte [] result1 = des.doFinal();System.out.println(new String (result1));
特点:
1、对称加密算法里,使用NOPadding,加密的明文必须等于分组长度倍数,否则报错
2、没有指明加密模式和填充模式的,表示使用默认的 DES/ECB/PKCS5Padding
3、加密后的字节数组可以编码成hex、base64
4、要复现一个对称加密算法,需要得到明文,key、iv、mode、padding
5、明文、key、iv需要注意解析方式,看是utf-8还是hex,不一定都是字符串形式
6、ECB模式和CBC模式的区别
7、如果加密模式是ECB,则不需要加iv,加了的话包报错的
8、如果使用PKCS5Padding,会对加密的明文填充1字节至一个分组的长度
9、DES算法明文按64位进行分组加密
10、如果明文中有两个分组的内容相同,ECB会得到完全一样的密文,CBC不会
11、加密算法的结果通常与明文等长或者更长,如果变短了,可能是gzip、protobuf,或者信息摘要算法
加密模式 对称加密算法中,不光有密钥,还有一个IV值,上方的演示因为是 ECB 模式,没有用到 IV 值,但是逆向中常见的是 CBC 模式,这个是存在一个IV向量值的
这个IV向量是有长度限制的,根据算法的分组长度不同,IV长度也不同,DES加密的IV向量占8个字节
ECB模式和CBC模式
这二者都是分组加密的形式
ECB模式,分八个字节一组,分别加密,得到密文
1 2 3 4 5 6 7 8 明文:sjjwsnb666 分割为: sjjwsnb6 66 sjjwsnb666 加密结果: Vmkul5panJ +bu+Jo4DZ7Lw== 56692e979a5a9c9f 9bbbe268e0367b2f sjjwsnb6 加密结果: Vmkul5panJ /+uVm31GQvyw== 56692e979a5a9c9f feb959b7d4642fcb 空格我自己加的,可以看到有很明显的分组痕迹,各管各的,拿出一段密文就可以获取部分明文,甚至可以自己加密一部分密文替换掉,达到替换明文的目的
ECB模式是不安全的
CBC模式
1 2 3 4 5 6 7 8 9 10 11 12 13 明文:sjjwsnb666 IV: 12345678 明文分组: sjjwsnb6 66 CBC模式处理时先异或操作 sjjwsnb6 & 12345678 再将异或的结果进行加密操作 假如结果是:asddfgfh 再将下一组和上一组加密的结果异或 asddfgfh & 66 再进行加密,这样前后串连起来,断绝了更改密文影响明文的操作,更加的安全
CBC加密模式实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 SecretKeySpec secretKeySpec = new SecretKeySpec ("123454678" .getBytes(), "DES" ); IvParameterSpec ivParameterSpec = new IvParameterSpec ("12345678" .getBytes()); Cipher des = Cipher.getInstance("DES/CBC/PKCS5Padding" ); des.init(Cipher.ENCRYPT_MODE ,secretKeySpec, ivParameterSpec); byte [] result = des.doFinal("demo" .getBytes()); String hexStr = ByteString.of(result).hex(); System.out.println(hexStr); System.out.println(Base64.getEncoder().encodeToString(result)); des.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); byte [] result1 = des.doFinal(result); System.out.println(new String (result1));
填充方式 “DES/CBC/PKCS5Padding”中 CBC 是加密模式,PKCS5Padding是填充方式
NOPadding方式:必须是整组的数据,否则加密失败,拿到ECB的整组数据,进行NOPadding就可以得到明文
PKCS5Padding填充:如果使用了PKCS5Padding填充,填充的是一个字节到一个分组的长度,这就是为啥刚好8位明文,还会增加一个空分组的原因,如果最后只有一个字节,那么就会填充7个字节上去
DES密钥的漏洞 DES的密钥虽然是64个字节,但是真正使用的只有56个字节,每个字节的最后一位会被舍去,看一下演示
1 2 3 4 5 6 7 8 9 10 先令密钥位12345678 字节表示为:00110001 00110010 00110011 00110100 00110101 00110110 00110111 00111000 将字节最后一位随便改,这里都改成0了: 00110000 00110010 00110010 00110100 00110100 00110110 00110110 00111000 转换成数字为:02244668 使用12345678来加密字符串,结果为96d0028878d58c89feb959b7d4642fcb 使用02244668来加密字符串,结果为96d0028878d58c89feb959b7d4642fcb
加密结果是一样的,最后一个bit位是不重要的,也就是说可以有好几个密钥来进行加解密得到同样的结果
DESede算法 又叫3DES,DESede算法进行三次DES操作,需要24位密钥,先用前八位密钥进行DES加密,再用中间八位密钥进行DES解密,最后用剩下的八位密钥进行DES加密。所以说如果前两组的密钥是一样的话,就相当于只用了后八位进行一次DES加密,效果是一样的
同样的有密钥问题
看看怎么用
1 2 3 4 5 6 7 8 9 10 SecretKeySpec secretKeySpec = new SecretKeySpec ("123456781234567812345678" .getBytes(), "DESede" ); DESedeKeySpec deskeySpec = new DesedeKeySpec ("123456788765432112345678" .getBytes()) IvParameterSpec iv = new IvParameterSpec ("12345678" .getBytes()); Cipher desede = Cipher.getInstance("DESede/CBC/PKCS5Padding" ); desede.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); desede.doFinal("demo" .getBytes());
AES算法 DES算法还是有很大的问题的,现在使用AES算法居多
根据密钥长度不同,分为AES128、AES192、AES256。分别对应16、20、32个字节
AES
密钥长度(bit)
分组长度(bit)
向量长度(bit)
加密轮数
AES-128
128
128
128
10
AES-192
192
128
128
12
AES-256
256
128
128
14
实现
AES算法明文按128位进行分组加密,其余特征与DES一致
1 2 3 4 5 6 7 8 SecretKeySpec secretKeySpec = new SecretKeySpec ("1234567890abcdef" .getBytes(), "AES" ); AlgorithmParameterSpec iv = new IvParameterSpec ("1234567890abcdef" .getBytes()); Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding" ); aes.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); aes.doFinal("demodemodemo1234567890abcdef" .getBytes());
通杀hook DES 看到这就可以发现,hook的函数其实是大同小异的,主要的是看hook的一个思路
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 var des = Java .use ("javax.crypto.Cipher" )des.init .overload ('int' , 'java.security.cert.Certificate' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.cert.Certificate')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' , 'java.security.SecureRandom' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key', 'java.security.SecureRandom')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.cert.Certificate' , 'java.security.SecureRandom' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' , 'java.security.spec.AlgorithmParameterSpec' , 'java.security.SecureRandom' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' , 'java.security.AlgorithmParameters' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key', 'java.security.AlgorithmParameters')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' , 'java.security.AlgorithmParameters' , 'java.security.SecureRandom' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom')" ) console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' , 'java.security.spec.AlgorithmParameterSpec' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + "init key" ; var key = arguments [1 ].getEncoded (); toBase64 (tag, key); toHex (tag, key); toUtf8 (tag, key); var tag = algorithm + "init iv" ; var iv = Java .cast (arguments [2 ], Java .use ("javax.crypto.spec.IvParameterSpec" )); iv = iv.getIV (); toBase64 (tag, iv); toHex (tag, iv); toUtf8 (tag, iv); console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.init .overload ('int' , 'java.security.Key' ).implementation = function ( ) { console .log ("Cipher init('int', 'java.security.Key')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + "init key" ; var key = arguments [1 ].getEncoded (); toBase64 (tag, key); toHex (tag, key); toUtf8 (tag, key); console .log ("=============================================================================" ); return this .init .apply (this , arguments ); } des.doFinal .overload ().implementation = function ( ) { console .log ("Cipher doFinal()" ) console .log ("================================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ("java.nio.ByteBuffer" , "java.nio.ByteBuffer" ).implementation = function ( ) { console .log ("Cipher doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer')" ) console .log ("================================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ('[B' ).implementation = function ( ) { console .log ("Cipher doFinal('[B')" ) console .log ("================================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ('[B' , 'int' ).implementation = function ( ) { console .log ("Cipher doFinal('[B', 'int')" ) console .log ("=======================================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ('[B' , 'int' , 'int' , '[B' ).implementation = function ( ) { console .log ("Cipher doFinal('[B', 'int', 'int', '[B')" ) console .log ("===============================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ('[B' , 'int' , 'int' , '[B' , 'int' ).implementation = function ( ) { console .log ("Cipher doFinal('[B', 'int', 'int', '[B', 'int')" ) console .log ("===================================" ); return this .doFinal .apply (this , arguments ); } des.doFinal .overload ('[B' , 'int' , 'int' ).implementation = function ( ) { console .log ("Cipher doFinal('[B', 'int', 'int')" ) showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " doFinal data" ; var data = arguments [0 ]; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); var result = this .doFinal .apply (this , arguments ); var tag = algorithm + " doFinal result" ; toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("=======================================================" ); return result; } des.doFinal .overload ('[B' ).implementation = function ( ) { console .log ("Cipher doFinal('[B')" ) showStacks (); var algorithm = this .getAlgorithm (); var tag = algorithm + " doFinal data" ; var data = arguments [0 ]; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); var result = this .doFinal .apply (this , arguments ); var tag = algorithm + " doFinal result" ; toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("===============================================" ); return result; }
这个不只是hookDES算法,DESede、AES和RSA都包含在内的,通杀了属于是
DESede 看一下两个加密方法的差别
可以看到中加密方式几乎使用的方法是一模一样的,DES直接就给他杀了,不用写其他的了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 SecretKeySpec secretKeySpec = new SecretKeySpec ("123454678" .getBytes(), "DES" );Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding" );des.init(Cipher.ENCRYPT_MODE, secretKeySpec); des.doFinal("demo" .getBytes()); SecretKeySpec secretKeySpec = new SecretKeySpec ("123456781234567812345678" .getBytes(), "DESede" ); IvParameterSpec iv = new IvParameterSpec ("12345678" .getBytes()); Cipher desede = Cipher.getInstance("DESede/CBC/PKCS5Padding" ); desede.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); desede.doFinal("demo" .getBytes());
AES 同上,一起杀了,就是IV不一样,但是不需要hook这个iv
1 2 3 4 5 6 7 8 SecretKeySpec secretKeySpec = new SecretKeySpec ("1234567890abcdef" .getBytes(), "AES" ); AlgorithmParameterSpec iv = new IvParameterSpec ("1234567890abcdef" .getBytes()); Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding" ); aes.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv); aes.doFinal("demodemodemo1234567890abcdef" .getBytes());
非对称加密算法 RSA RSA算法是一个非对称加密算法,就是说加密和解密不使用同一个密钥,分为公钥和私钥,公钥用来加密数据,私钥用来解密数据
私钥的格式 pkcs1格式通常开头是 -----BEGIN RSA PRIVATE KEY -----
pkcs8格式通常开头是 -----BEGIN PRIVATE KEY-----
java中的私钥必须是pkcs8格式,如果得到了pkcs1,需要转成pkcs8
https://www.ssleye.com/ssltool/pkcs.html
http://web.chacuo.net/netrsakeypair
RSA密钥的解析 去在线网站上弄一个没有密码的RSA密钥对
公钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static void pubKey () throws InvalidKeySpecException, NoSuchAlgorithmException { String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kIa+rcK0oirgwxOM9tIkW95i" + "GNpo7tmiR8KLOapz13kYcy0csBeIJ6R44J3KEWVQcrjSqkPGeQtOrPYl1LxX5evb" + "DsIjmKBZXu4w8FkFxCW0ItaJu2qrX+3vr14g+JXcOYrcED+l53pwQ5sls1bbHqAe" + "V+I+MfaZ2XWYTNRbRwIDAQAB" ; byte [] keyBytes = ByteString.decodeBase64(key).toByteArray(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); PublicKey publicKey = keyFactory.generatePublic(keySpec); byte [] keyBytes = publickey.getEncode(); System.out.println(Base64.getEncoder().encodeToString(bytes)); }
私钥
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 public static void staticKey () throws InvalidKeySpecException, NoSuchAlgorithmException { String stakey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALaQhr6twrSiKuDD" + "E4z20iRb3mIY2mju2aJHwos5qnPXeRhzLRywF4gnpHjgncoRZVByuNKqQ8Z5C06s" + "9iXUvFfl69sOwiOYoFle7jDwWQXEJbQi1om7aqtf7e+vXiD4ldw5itwQP6XnenBD" + "myWzVtseoB5X4j4x9pnZdZhM1FtHAgMBAAECgYBDKOW4zZk79BBMANd3WvExWO51" + "LeljAsLjFPz3VK5k0RaGLRCiZhEyEEtMAG1rgXzA3IMrVGF8aNkFB1HB1wG1wC4P" + "t7hZl5s2tdseXsIMmS0mpDUZZu+tnFwHcqZS4K6rIFjQ8lZn7epv32Pc/0aSliWp" + "5II5OZG2rrcG5C+y4QJBAPDQHba3wbuirDkq1QE3qUv05RAXGurideos9audGbf7" + "RXu4RjiYT7xlEVdZBARL/eAu36PDZy+GTb6No5Iqn5ECQQDCE//3YVKgUbfMDLrq" + "2LznXk+S5NXNbj2tocKsvQJLIa29qfd3eouQpR1osCJvqmA8aV2Z90y03yFBUOmv" + "B5FXAkEAzyp7JYGYDQ+5EcUjUdTMtCeOF/WIlqETx82921FfmsNz1yeEYZPGpNBd" + "xsMxjXDCi2ZHxt6Hmn7zywaWvVwlwQJALFNVCrL3pBYF3Fyr9Cc8PbuUgQAytJCR" + "Fa70P2+Lro0qmT7QfkFGzupnJRnVQ5uuDx4heqC4rDap6bkJJiicUQJAfWRkNXyX" + "WFvfM04BMjWSBgBOUmAVyB8GFAGh3e4uUvZYIr9oAnzdErFLDdXUyl1uCw5qrYkj" + "hM7Kq5Lkby/iaw==" ; byte [] keyBytes = ByteString.decodeBase64(stakey).toByteArray(); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); Log.d("demo" , ByteString.of(privateKey.getEncoded().base64())) }
虽然要是64的倍数,但是一般是 512,1024,2048这样的
RSA加解密 实现
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 39 40 41 42 43 public static void RSACipher () throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kIa+rcK0oirgwxOM9tIkW95i\n" + "GNpo7tmiR8KLOapz13kYcy0csBeIJ6R44J3KEWVQcrjSqkPGeQtOrPYl1LxX5evb\n" + "DsIjmKBZXu4w8FkFxCW0ItaJu2qrX+3vr14g+JXcOYrcED+l53pwQ5sls1bbHqAe\n" + "V+I+MfaZ2XWYTNRbRwIDAQAB" ; byte [] keyBytes = ByteString.decodeBase64(key).toByteArray(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); PublicKey publicKey = keyFactory.generatePublic(keySpec); byte [] bytes = publicKey.getEncoded(); String stakey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALaQhr6twrSiKuDD" + "E4z20iRb3mIY2mju2aJHwos5qnPXeRhzLRywF4gnpHjgncoRZVByuNKqQ8Z5C06s" + "9iXUvFfl69sOwiOYoFle7jDwWQXEJbQi1om7aqtf7e+vXiD4ldw5itwQP6XnenBD" + "myWzVtseoB5X4j4x9pnZdZhM1FtHAgMBAAECgYBDKOW4zZk79BBMANd3WvExWO51" + "LeljAsLjFPz3VK5k0RaGLRCiZhEyEEtMAG1rgXzA3IMrVGF8aNkFB1HB1wG1wC4P" + "t7hZl5s2tdseXsIMmS0mpDUZZu+tnFwHcqZS4K6rIFjQ8lZn7epv32Pc/0aSliWp" + "5II5OZG2rrcG5C+y4QJBAPDQHba3wbuirDkq1QE3qUv05RAXGurideos9audGbf7" + "RXu4RjiYT7xlEVdZBARL/eAu36PDZy+GTb6No5Iqn5ECQQDCE//3YVKgUbfMDLrq" + "2LznXk+S5NXNbj2tocKsvQJLIa29qfd3eouQpR1osCJvqmA8aV2Z90y03yFBUOmv" + "B5FXAkEAzyp7JYGYDQ+5EcUjUdTMtCeOF/WIlqETx82921FfmsNz1yeEYZPGpNBd" + "xsMxjXDCi2ZHxt6Hmn7zywaWvVwlwQJALFNVCrL3pBYF3Fyr9Cc8PbuUgQAytJCR" + "Fa70P2+Lro0qmT7QfkFGzupnJRnVQ5uuDx4heqC4rDap6bkJJiicUQJAfWRkNXyX" + "WFvfM04BMjWSBgBOUmAVyB8GFAGh3e4uUvZYIr9oAnzdErFLDdXUyl1uCw5qrYkj" + "hM7Kq5Lkby/iaw==" ; byte [] staKeyBytes = ByteString.decodeBase64(stakey).toByteArray(); PKCS8EncodedKeySpec staKeySpec = new PKCS8EncodedKeySpec (staKeyBytes); KeyFactory staKeyFactory = KeyFactory.getInstance("RSA" ); PrivateKey privateKey = staKeyFactory.generatePrivate(staKeySpec); Cipher cipher = Cipher.getInstance("RSA/None/NOPadding" ); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte [] bt_encrypted = cipher.doFinal("1232345456" .getBytes()); Cipher cipher1 = Cipher.getInstance("RSA/None/NOPadding" ); cipher1.init(Cipher.DECRYPT_MODE, privateKey); byte [] bt_original = cipher1.doFinal(bt_encrypted); }
可以明显的看到RSA的实现还是cipher,那么DES的代码改都不用改
所谓的加密结果不固定就是使用padding的时候 bt_encrypted 的值不固定,会发生变化,但是NOpadding就是固定的
RSAd的加密模式和填充方式
RSA模式和填充细节
1、None模式与ECB模式是一致的
2、NOPadding
明文最多字节数为密钥字节数,如果密钥1024bit,最多加密1024bit
密文与密钥等长
填充字节0(不是字符是占8个bit位的字节),加密后的密文不变
NOPadding解密之后还需要进行去零,0是byte数组中的0,在数据的前面添加的
3、PKCS1Padding
明文最大字节数为密钥字节数 -11,如果密钥128字节,最多加密117字节,剩余的随机填充
密文与密钥等长
因为是随机填充,每一次的填充不一样,使得加密后的密文会变
有个小秘密,PKCS1Padding解密的时候不指明是会自动取出前面的填充,指明了反而不去除
RSA密钥的转换 全面使用的密钥都是使用base64编码的,也有hex的密钥
1 2 3 4 5 6 7 8 9 10 11 12 // hex不是简单的将base64的结果解码然后hex编码 将转换后的数据进行这样的分割 30819f300d06092a864886f70d010101050003818d0030818902818100 | bdab976385934fb3ca17ec8775da752e191ce4ef6b5dc6398b3faf60615d45ad06e01216f8f11f4cab9e43a789b296a7bb318882bf320d2a21c00f6da233607576de16e7f6f552d97c2a4db345c97db5b5fc15127bd77ff1f1f588c577959d62694819a7eecb1f23d91c45654fbe90f300f68b64429cd4770d6685a761a1ed2d | 0203 | 010001 // 这两部分没啥用了 30819f300d06092a864886f70d010101050003818d0030818902818100 0203 // 需要操作这两部分 RSA 的核心原理就是两个大数相乘 bdab976385934fb3ca17ec8775da752e191ce4ef6b5dc6398b3faf60615d45ad06e01216f8f11f4cab9e43a789b296a7bb318882bf320d2a21c00f6da233607576de16e7f6f552d97c2a4db345c97db5b5fc15127bd77ff1f1f588c577959d62694819a7eecb1f23d91c45654fbe90f300f68b64429cd4770d6685a761a1ed2d 010001
1 2 3 4 5 6 7 8 9 10 11 12 public static String modulus = "bdab976385934fb3ca17ec8775da752e191ce4ef6b5dc6398b3faf60615d45ad06e01216f8f11f4cab9e43a789b296a7bb318882bf320d2a21c00f6da233607576de16e7f6f552d97c2a4db345c97db5b5fc15127bd77ff1f1f588c577959d62694819a7eecb1f23d91c45654fbe90f300f68b64429cd4770d6685a761a1ed2d" public static String publicExponent = "010001" BigInteger n = new BigInteger (modulus, 16 );BigInteger e = new BigInteger (publicExponent, 16 );RSAPublicKeySpec spec = new RSAPublicKeySpec (n, e);Public publicKey = keyFactory.generatePublic(spec);byte [] publicBytes = publicKey.getEncoded();System.out.printn(Base64.getEncoded().encodeToString(pubkeyBytes));
还可以利用工具转换 ==opensll==
1 2 3 4 5 // 这样即可输出公钥hex的编码内容 opensll rsa -public -in [文件] -text // 私钥 opensll res -in [] -text
具体的加密解密操作和base64是一样的
RSAhook 其他的方法不用管,可以直接用之前hookDES的代码通杀hook,但是hex编码的密钥不能无脑 getEncoded了,需要进行一个判断
RSA的私钥无法进行 getEncoded ,这样就无法获取私钥,但是一般逆向也不需要RSA的私钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 des.init.overload('int' , 'java.security.Key' ).implementation = function () { console.log("Cipher init('int', 'java.security.Key')" ) var algorithm = this .getAlgorithm(); var tag = algorithm + "init key" ; var className = JSON.stringify(arguments[1 ]); if (className.indexOf("SecretKeySpec" ) === -1 ){ var key = arguments[1 ].getEncoded(); toBase64(tag, key); toHex(tag, key); toUtf8(tag, key); } console.log("================================================================" ); return this .init.apply(this , arguments); }
算法常见の套路 无论是对称加密算法还是非对称加密算法都有局限性,如果仅使用对称加密算法,使用固定的密钥的话,仅破解客户端,所有的东西都可以解密了,随机生成密钥的话,还需要传输密钥,更不安全。仅使用非对称加密算法能够加密的数据太有限了。
常见的套路就是AES+RSA,先随机生成AES密钥,将这个密钥进行RSA加密。得到加密密文,将数据密文和密钥密文发送给服务器。
数字签名算法 所谓数字签名算法是将信息摘要算法和RSA算法进行结合,先进行信息摘要算法,再进行非对称加密算法
主要作用就是用来防止发出去的数据被篡改
1 2 3 4 我要发送数据 "demo" 为了防止这个数据被篡改我先加一个信息摘要MD5 fe01ce2a7fbac8fafaed7c982a04e229 这样还有被篡改的可能,对方可以测试我使用的什么信息摘要算法,更改数据之后,再进行摘要算法 我就再给他一个RSA加密,使用私钥进行加密,公钥对外公开,对方可以用公钥来验证数据,这样就难以篡改了,除非我的私钥被拿到
签名实现
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 public static void MD5withRSA () throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { String stakey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALaQhr6twrSiKuDD" + "E4z20iRb3mIY2mju2aJHwos5qnPXeRhzLRywF4gnpHjgncoRZVByuNKqQ8Z5C06s" + "9iXUvFfl69sOwiOYoFle7jDwWQXEJbQi1om7aqtf7e+vXiD4ldw5itwQP6XnenBD" + "myWzVtseoB5X4j4x9pnZdZhM1FtHAgMBAAECgYBDKOW4zZk79BBMANd3WvExWO51" + "LeljAsLjFPz3VK5k0RaGLRCiZhEyEEtMAG1rgXzA3IMrVGF8aNkFB1HB1wG1wC4P" + "t7hZl5s2tdseXsIMmS0mpDUZZu+tnFwHcqZS4K6rIFjQ8lZn7epv32Pc/0aSliWp" + "5II5OZG2rrcG5C+y4QJBAPDQHba3wbuirDkq1QE3qUv05RAXGurideos9audGbf7" + "RXu4RjiYT7xlEVdZBARL/eAu36PDZy+GTb6No5Iqn5ECQQDCE//3YVKgUbfMDLrq" + "2LznXk+S5NXNbj2tocKsvQJLIa29qfd3eouQpR1osCJvqmA8aV2Z90y03yFBUOmv" + "B5FXAkEAzyp7JYGYDQ+5EcUjUdTMtCeOF/WIlqETx82921FfmsNz1yeEYZPGpNBd" + "xsMxjXDCi2ZHxt6Hmn7zywaWvVwlwQJALFNVCrL3pBYF3Fyr9Cc8PbuUgQAytJCR" + "Fa70P2+Lro0qmT7QfkFGzupnJRnVQ5uuDx4heqC4rDap6bkJJiicUQJAfWRkNXyX" + "WFvfM04BMjWSBgBOUmAVyB8GFAGh3e4uUvZYIr9oAnzdErFLDdXUyl1uCw5qrYkj" + "hM7Kq5Lkby/iaw==" ; byte [] staKeyBytes = ByteString.decodeBase64(stakey).toByteArray(); PKCS8EncodedKeySpec staKeySpec = new PKCS8EncodedKeySpec (staKeyBytes); KeyFactory staKeyFactory = KeyFactory.getInstance("RSA" ); PrivateKey privateKey = staKeyFactory.generatePrivate(staKeySpec); Signature sig = Signature.getInstance("SHA256withRSA" ); sig.initSign(privateKey); sig.update("demo" .getBytes()); byte [] signResult = sig.sign(); }
验证实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kIa+rcK0oirgwxOM9tIkW95i\n" + "GNpo7tmiR8KLOapz13kYcy0csBeIJ6R44J3KEWVQcrjSqkPGeQtOrPYl1LxX5evb\n" + "DsIjmKBZXu4w8FkFxCW0ItaJu2qrX+3vr14g+JXcOYrcED+l53pwQ5sls1bbHqAe\n" + "V+I+MfaZ2XWYTNRbRwIDAQAB" ; byte [] keyBytes = ByteString.decodeBase64(key).toByteArray(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); PublicKey publicKey = keyFactory.generatePublic(keySpec); Signature sig = Signature.getInstance("SHA256withRSA" ); sig.initVerify(publicKey); sig.update("demo" .getBytes()); boolean isTrue = sig.verify(signResult); System.out.println(isTrue);
这个签名算法的结果是加密之后前面,将信息摘要算法的结果放在最后面,然后在前面填充东西,具体填充的啥还不知道
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 39 40 41 42 public static void RSAwithRSA () throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException { String stakey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALaQhr6twrSiKuDD" + "E4z20iRb3mIY2mju2aJHwos5qnPXeRhzLRywF4gnpHjgncoRZVByuNKqQ8Z5C06s" + "9iXUvFfl69sOwiOYoFle7jDwWQXEJbQi1om7aqtf7e+vXiD4ldw5itwQP6XnenBD" + "myWzVtseoB5X4j4x9pnZdZhM1FtHAgMBAAECgYBDKOW4zZk79BBMANd3WvExWO51" + "LeljAsLjFPz3VK5k0RaGLRCiZhEyEEtMAG1rgXzA3IMrVGF8aNkFB1HB1wG1wC4P" + "t7hZl5s2tdseXsIMmS0mpDUZZu+tnFwHcqZS4K6rIFjQ8lZn7epv32Pc/0aSliWp" + "5II5OZG2rrcG5C+y4QJBAPDQHba3wbuirDkq1QE3qUv05RAXGurideos9audGbf7" + "RXu4RjiYT7xlEVdZBARL/eAu36PDZy+GTb6No5Iqn5ECQQDCE//3YVKgUbfMDLrq" + "2LznXk+S5NXNbj2tocKsvQJLIa29qfd3eouQpR1osCJvqmA8aV2Z90y03yFBUOmv" + "B5FXAkEAzyp7JYGYDQ+5EcUjUdTMtCeOF/WIlqETx82921FfmsNz1yeEYZPGpNBd" + "xsMxjXDCi2ZHxt6Hmn7zywaWvVwlwQJALFNVCrL3pBYF3Fyr9Cc8PbuUgQAytJCR" + "Fa70P2+Lro0qmT7QfkFGzupnJRnVQ5uuDx4heqC4rDap6bkJJiicUQJAfWRkNXyX" + "WFvfM04BMjWSBgBOUmAVyB8GFAGh3e4uUvZYIr9oAnzdErFLDdXUyl1uCw5qrYkj" + "hM7Kq5Lkby/iaw==" ; byte [] staKeyBytes = ByteString.decodeBase64(stakey).toByteArray(); PKCS8EncodedKeySpec staKeySpec = new PKCS8EncodedKeySpec (staKeyBytes); KeyFactory staKeyFactory = KeyFactory.getInstance("RSA" ); PrivateKey privateKey = staKeyFactory.generatePrivate(staKeySpec); Signature sig = Signature.getInstance("SHA256withRSA" ); sig.initSign(privateKey); sig.update("demo" .getBytes()); byte [] signResult = sig.sign(); String key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2kIa+rcK0oirgwxOM9tIkW95i\n" + "GNpo7tmiR8KLOapz13kYcy0csBeIJ6R44J3KEWVQcrjSqkPGeQtOrPYl1LxX5evb\n" + "DsIjmKBZXu4w8FkFxCW0ItaJu2qrX+3vr14g+JXcOYrcED+l53pwQ5sls1bbHqAe\n" + "V+I+MfaZ2XWYTNRbRwIDAQAB" ; byte [] keyBytes = ByteString.decodeBase64(key).toByteArray(); X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA" ); PublicKey publicKey = keyFactory.generatePublic(keySpec); sig.initVerify(publicKey); sig.update("demo" .getBytes()); boolean isTrue = sig.verify(signResult); System.out.println(isTrue); }
数字签名算法hook通杀 从实现可以看出,可hook的方法有 ==getPrivateKey== 、initSign、 update、 sign、initVerify、verify
hook update方法获取明文
hook sign方法获取密文
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 var signature = Java .use ("java.security.Signature" )signature.update .overload ('byte' ).implementation = function (data ) { console .log ("Signature update('byte)" ) console .log ("===============================================" ); return this .update (data); } signature.update .overload ('java.nio.ByteBuffer' ).implementation = function (data ) { console .log ("Signature update('java.nio.ByteBuffer')" ) console .log ("===============================================" ); return this .update (data); } signature.update .overload ('[B' ).implementation = function (data ) { console .log ("Signature update('[B')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Signature update('[B')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("===============================================" ); return this .update (data); } signature.update .overload ('[B' , 'int' , 'int' ).implementation = function (data, start, len ) { console .log ("Signature update('[B', 'int', 'int')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Signature update('[B', 'int', 'int')" ; toBase64 (tag, data); toHex (tag, data); toUtf8 (tag, data); console .log ("===============================================" , start, len); return this .update (data, start, len); } signature.sign .overload ('[B' , 'int' , 'int' ).implementation = function ( ) { console .log ("Signature sign('[B', 'int', 'int')" ) console .log ("===============================================" ); return this .sign .apply (this , arguments ); } signature.sign .overload ().implementation = function ( ) { console .log ("Signature sign()" ) var result = this .sign (); var algorithm = this .getAlgorithm (); var tag = algorithm + " Signature sign()" ; toBase64 (tag, result); toHex (tag, result); toUtf8 (tag, result); console .log ("===============================================" ); return result; }
verify 方法能获取密文,已经获取到了,再hook起来没有什么意义
initSign和initVerify方法可以获取到 公钥和私钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 signature.initSign .overload ('java.security.PrivateKey' ).implementation = function (key ) { console .log ("Signature initSign('java.security.PrivateKey')" ) var algorithm = this .getAlgorithm (); var tag = algorithm + " Signature initSign()" ; var OpenSSLRSAPrivateKey = Java .use ("com.android.org.conscrypt.OpenSSLRSAPrivateKey" ); var realkey = Java .cast (key, OpenSSLRSAPrivateKey ).getEncoded (); toBase64 (tag, realkey); toHex (tag, realkey); toUtf8 (tag, realkey); console .log ("================================================" ); return this .initSign (key); }
这个密钥是不能直接获取,因为传入的是PrivateKey类 ,PrivateKey 是一个接口,没有像之前一样的 getEncoded 方法,所以需要做一个多态转型,来获取,而且只能获取到base64的,不能获取到hex的密钥,有用处但是用处不大
CryptoJS 学一下JS的加解密,因为可能要复现某算法,使用JS脚本来hook复现比较方便,而且被其他语言调用也比较方便
使用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 27 28 29 30 31 32 33 var cryptoJS = require ('crypto-js' );let demo = "123456" ;let key = "demo" ;console .log ("MD5:" + cryptoJS.MD5 (demo).toString ());console .log ("HmacMD5:" + cryptoJS.HmacMD5 (demo, key).toString ())console .log ("SHA1:" + cryptoJS.SHA1 (demo).toString ())console .log ("HmacSHA1:" + cryptoJS.HmacSHA1 (demo, key).toString ())console .log ("SHA256:" + cryptoJS.SHA256 (demo).toString ())console .log ("HmacSHA256:" + cryptoJS.HmacSHA256 (demo, key).toString ())console .log ("SHA512:" + cryptoJS.SHA512 (demo).toString ())console .log ("HmacSHA512:" + cryptoJS.HmacSHA512 (demo, key).toString ())MD5 :e10adc3949ba59abbe56e057f20f883eHmacMD5 :e8649f37712cdad94724b22154e4b83bSHA1 :7c4a8d09ca3762af61e59520943dc26494f8941bHmacSHA1 :8a497921f4d842b57f6e054cee4fd2d91577815aSHA256 :8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92HmacSHA256 :1b8e71ff0087551bb6f063b15ea850650ddbdf6a02e5e81c2a981b91efafebddSHA512 :ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413HmacSHA512 :14f04320d0a4a21ed7517b94a8cf219f3e674fa95dd6281d5b61e3fc2e52a5567468e3ef4b4ace6edbecd6aa809f012606e482c367bbb7c285278a1bb3a0008d{ words : [ -519381959 , 1236949419 , -1101602729 , -233863106 ], sigBytes : 16 } console .log ("MD5:" + cryptoJS.MD5 (demo))
也存在一种,冗长,难写,复杂的实现形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var hasher = cryptoJS.algo .SHA256 .create ();hasher.reset (); hasher.update ("123456" ); hasher.update ("789" ); var hash = hasher.finalize ().toString ();console .log (hash);var hasherHmac = cryptoJS.algo .HMAC .create (cryptoJS.algo .SHA256 , "demo" );hasherHmac.reset (); hasherHmac.update ("123456" ); hasherHmac.update ("789" ); var hmac = hasherHmac.finalize ().toString ();console .log (hmac);
怎么说呢,放着简单的不用,用复杂的干啥呢
字符串解析 在Java中字符串解析是转换成 base64或者hex或者UTF-8,在JS中当然没什么不一样了,换个写法而已
哦,说一下之前的wordArray
1 2 3 4 5 6 { words : [ -519381959 , 1236949419 , -1101602729 , -233863106 ], sigBytes : 16 } 这个words是一个类似于字节数组的东西,将四个字节变成了一个整数,也可以理解成字节数组,反正就是存这个的
String转wordArray 1 2 3 4 5 6 7 8 cryptoJS.enc .Utf8 .parse (utf8ToString) cryptoJS.enc .Hex .parse (hexToString) cryptoJS.enc .Base64 .parse (base64ToString)
wordArray转String 1 2 3 4 5 6 7 8 9 10 11 let wordArray = cryptoJS.MD5 ("123456" )console .log (wordArray)console .log (wordArray + '' ) console .log (wordArray.toString ())console .log (wordArray.toString (cryptoJS.enc .Hex ))console .log (wordArray.toString (cryptoJS.enc .Base64 ))console .log (cryptoJS.enc .Hex .stringify (wordArray))console .log (cryptoJS.enc .Base64 .stringify (wordArray))
转UTF-8有报错,知道就行了,可以看到toString方法默认的是转成HEX编码
注意这个转base64是将字符数组转base64不是将hex转成base64编码,那样会变得很长,很显然这并没有发生
这些的用处在于有时候得到的数据千奇百怪,需要各种处理,比如得到hex编码的明文
1 2 3 4 5 6 7 // 如果直接这样操作的话,是当作utf8来解析的 var mdhex = "313233343536" console.log(cryptoJS.MD5(mdhex).toString()) // 这个时候就需要转换 var mdhex = "313233343536" console.log(cryptoJS.MD5(cryptoJS.enc.Hex.parse(mdhex)).toString())
对称加密算法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var cfg = { iv : iv, mode : cryptoJS.mode .CBC , padding : cryptoJS.pad .Pkcs7 , format : cryptoJS.format .Hex } 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var hexKeyBytes = cryptoJS.enc .Hex .parse ("0123456789ABCDEF" );var hexIvBytes = cryptoJS.enc .Hex .parse ("0123456789ABCDEF" );var cfg = { iv : hexIvBytes, mode : cryptoJS.mode .CBC , padding : cryptoJS.pad .Pkcs7 , format : cryptoJS.format .Hex } var encrypted = cryptoJS.AES .encrypt ("123456" , hexKeyBytes, cfg);console .log (encrypted.ciphertext .toString ());
注意:cfg中不指定输出形式的话,encrypted 默认以Base64的形式输出
1 2 3 4 5 console .log (encrypted.toString (cryptoJS.enc .Base64 ));console .log (encrypted.toString (cryptoJS.enc .Hex ));
format属性中还可以夹带私货,自定义输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var cfg = { iv : hexIvBytes, mode : cryptoJS.mode .CBC , padding : cryptoJS.pad .Pkcs7 , format : { stringify : function (data ){ let 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 } } }
解密也没啥难度,不写了
其他算法 RIPEMD160 和 HmacRIPEMD160 1 2 3 4 5 6 7 8 9 let ripemd160 = cryptoJS.RIPEMD160 ("123456" );console .log (ripemd160 + '' )let hmacRIPEMD160 = cryptoJS.HmacRIPEMD160 ("123456" , "demo" );console .log (hmacRIPEMD160 + '' )
PBKDF2 和 EvpKDF 1 2 3 4 5 6 7 8 let PBKDF2 = cryptoJS.PBKDF2 ("123456" , "demo" , { keySize : 8 , iterations : 1000 });console .log (PBKDF2 + '' )let EvpKDF = cryptoJS.EvpKDF ("123456" , "demo" , { keySize : 8 , iterations : 1000 });console .log (EvpKDF + '' )
jsencrypt 非对称加密算法 非对称加密算法(RSA)CryptoJS没有,使用jsencrypt库来实现。
这个库是服务于web开发的,纯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 26 27 28 29 30 31 32 33 34 35 36 37 38 const JSEncrypt = require ('jsencrypt' );var publicKey = "-----BEGIN PUBLIC KEY-----\n" + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9zDcwxIE6ivfU821isEn0jpQM\n" + "l+sSK+DA5fJYKEkaGUyOWyxLt2E89+wJx80Ezn2R62kK/NzQ84ZHH8bRG2P7upZ7\n" + "LkYCDg+9EzmWzbPPIeSiTv5jHOFGCSmlC58S45eq5WBf8qU2haa50x004m87iW4S\n" + "TrpCuA0mPjXP/ZkjcQIDAQAB\n" + "-----END PUBLIC KEY-----" ; var privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL3MNzDEgTqK99Tz\n" + "bWKwSfSOlAyX6xIr4MDl8lgoSRoZTI5bLEu3YTz37AnHzQTOfZHraQr83NDzhkcf\n" + "xtEbY/u6lnsuRgIOD70TOZbNs88h5KJO/mMc4UYJKaULnxLjl6rlYF/ypTaFprnT\n" + "HTTibzuJbhJOukK4DSY+Nc/9mSNxAgMBAAECgYBPNCqP8mEPYjDcMB2kwnaKVPmZ\n" + "a8hQU/k95nfErEMdXhNhkNCiZEty2u8ogbWf3N/wBfJXAIDRvd56Tdt1Jd4J1UJ+\n" + "osHNkmWfx9ld6GYWkN8JRFGjI8Gj3FTNKqWhWCTlnpvWsyyK4F6aCHIWD6f211lk\n" + "u6H1+r/q9zjvu1rNhQJBAOwTn38twXyW7CumZKfqtaBJfnhKOvVmDMyGia02oOx1\n" + "ZVL9stsoYKCC/RxcBEi6E3ZfQOeDyBGxN5UxCiojyMsCQQDN0L9sG2KJHHa7BuwA\n" + "lkXDVs6geE+a4SdNbHSp5JMfUPID7ul+r0wlnpljD7g3s/2+TkpqUseMiNPs5Hhb\n" + "GwkzAkBDyTapC/hcz/Esb3DDjm9sgO3hmF7pi83tBEyQAfmfK+5WMCalKyjjrfkD\n" + "paBNSbDA8oTudTaDbgFpw1UJ2JCVAkBxqlGtgMpAcunXjJEWGefZY72lvgwouyQb\n" + "jEQ597SQ3QFrzqxBfMqPFDIeFXZlvQ/r5A0Q/zqZkI+KCvu1RQ8lAkAZEhjGasXX\n" + "SNZuX0FZzcAUEs/rkhrPL4pqNg5XXda/nzYHdxdPVMsgv8A3RSKBc7P+2wMrB075\n" + "yCszbgV/ovWx\n" + "-----END PRIVATE KEY-----" ; function setEncrypt (msg) { const jsencrypt = new JSEncrypt (); jsencrypt.setPublicKey (publicKey); return jsencrypt.encrypt (msg); } console .log (setEncrypt ("123456" ));
解决这个报错
1 2 3 windows = global; 或者 var windows = global;
还有这个报错
这个报错是由于错误的引用导致的
1 const JSEncrypt = require ('jsencrypt' );
虽然底下这俩和cryptoJS一样冒绿光,但是就是报错,很奇怪
调教好之后运行
看结果很明显是 PKCS1Padding 的填充,如果想使用其他填充模式,就需要一些操作了
JSEncrypt添加NOPadding填充 之前了解到NOPadding的填充是在明文前面填充字节0,如果我想实现的话就可以进行手动填充
打个断点进去
结果在这直接return了,从这里进去
导出了一个方法,这个方法是用来将 hex 的十六进制数据转换成 base64 编码
继续进,getkey 方法是检验之前是否传入了key,不管,继续
进入加密方法,这个是关键方法
maxLength 是检测最大字符长度,不用管他,PKSC1Padding 和 NOPadding 都需要这个数
经过 pkcs2 处理后进行判断是否有内容,有内容就进行加密,然后进行hex编码,再进行hex转base64将结果返回。
这里就直接进PKSC1模式进行加密了,可以在这里加一个判断,让用户多传进来一个字符串,来判断模式,这样就可以二者兼得
开始尝试,依照pkcs写Nopadding照葫芦画瓢了
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 function nopkcs (s, n ) { if (n < s.length ) { console .error ("Message too long for RSA" ) return null ; } var ba = []; var i = s.length - 1 ; while (i >= 0 && n > 0 ) { var c = s.charCodeAt (i--); if (c < 128 ) { ba[--n] = c; } else if ((c > 127 ) && (c < 2048 )) { ba[--n] = (c & 63 ) | 128 ; ba[--n] = (c >> 6 ) | 192 ; } else { ba[--n] = (c & 63 ) | 128 ; ba[--n] = ((c >> 6 ) & 63 ) | 128 ; ba[--n] = (c >> 12 ) | 224 ; } } while (n > 0 ) { ba[--n] = 0 ; } return new BigInteger (ba); }
添一个判断model的逻辑
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 RSAKey .prototype .encrypt = function (text, model ) { var maxLength = (this .n .bitLength () + 7 ) >> 3 ; var m; if (model == "PKCSPadding" || model == undefined || model == null ) { m = pkcs1pad2 (text, maxLength); } else if (model == "NOPadding" ) { m = nopkcs (text, maxLength) } else { console .error ("Invalid padding model" ); return null ; } if (m == null ) { return null ; } var c = this .doPublic (m); if (c == null ) { return null ; } var h = c.toString (16 ); var length = h.length ; for (var i = 0 ; i < maxLength * 2 - length; i++) { h = "0" + h; } return h; };
更改传入值,可以发现这之只是改了加密,还没改解密,不着急,先让这个传入两个参数,base形式的就先放一放
在相应的文件中更改,打断点调试的时候还是源代码,待我稍作思量,更益其巧
找到这个文件,上面有各个文件的信息啥的,代码没有被解析,怎么说呢,直接改吧
调用时,多接收一个参数
// 处理
1 2 3 4 5 6 判断方法 \n var m;\n // 还是保留默认的是PKCS#1\n if (model == 'PKCSPadding' || model == undefined || model == null) {/n m = pkcs1pad2(text, maxLength);/n } else if (model == 'NOPadding') {\n m = nopkcs(text, maxLength);\n } else {\n console.error('Invalid padding model');\n returm null;\n } Nopadding填充操作 function nopkcs(s, n) {\n if (n < s.length) {\n console.error('Message too long for RSA');\n return null;\n }\n var ba = [];\n var i = s.length - 1;\n while (i >= 0 && n > 0) {\n var c = s.charCodeAt(i--);\n if (c < 128) {\n ba[--n] = c;\n }\n else if ((c > 127) && (c < 2024)) {\n ba[--n] = (c & 63) | 128;\n ba[--n] = (c >> 6) | 192;\n }\n else {\n ba[--n] = (c & 63) | 128;\n ba[--n] = ((c >> 6) & 63) | 128;\n ba[--n] = (c >> 12) | 224;\n } \n }\n // 前面的都一样,但是处理完数据之后,就改填充0了,这时候就比PKSC简单了\n while (--n > 0) {\n ba[--n] = 0;\n }\n return new _jsbn__WEBPACK_IMPORTED_MODULE_0__.BigInteger(ba);\n}
大功告成
验证一下
无需多言,还有解密
接下来写NOPadding的解密
和加密的套路差不多,先检验是否有私钥,然后获取 base64转hex 的方法,将密文转成hex
然后就是重点了
这个C是处理hex数据的和解密无关,m我也看了一下直接用就行,真正处理的在 return上,这样也省事了,开始微操
加一个判断,通过打断点发现,这个m比较短,也就是说很可能就是明文的一种编码,反复实验之后先将这个 BigInteger 类型的数据转换成 byte 数组,然后将byte数组转换成utf-8的数据,有点弯弯绕。
1 2 3 4 5 6 7 8 >return m; >BigInteger { '0': 53753142, '1': 201507, s: 0, t: 2 } >return m.toByteArray(); >[ 49, 50, 51, 52, 53, 54 ] >return String.fromCharCode.apply(null, m.toByteArray()); >123456
需要更改的位置有
这个是调试视图,去那一堆没解析的乱码里去改!就不放图片了