数字签名

数字签名介绍

数字签名是非对称密钥加密技术与数字摘要技术的应用,可以用来保证数据不可伪造并且确认数据是由谁发送的

简单来说就是数字摘要(md5、sha系列。。)和RSA的结合,即对明文数据进行数字摘要后,再使用RSA的私钥对数字摘要hash值进行加密,最后通过RSA的公钥解密验证。

这么做的原因是通过数字摘要的靠伪造性可以保证数据不可伪造。

  • 如果修改了消息,接收方可以对消息进行哈希后和数字摘要验证是否一致得知消息是否被伪造。
  • 但是为了防止攻击者如果同时篡改了消息和数字摘要(在数字摘要算法泄露的情况下,如使用常规的公开的md5),需要使用RSA再进行一次加密,通过私钥加密公钥解密验证的方式,可以保证数据不可伪造并且根据公钥的对应信息可以得知
  • 由于攻击者无法得知加密者的私钥,因而无法伪造RSA加密。
  • 这俩个缺一不可,缺少了RSA安全性降低,缺少了数字摘要,则效率降低,因为RSA处理大数据的效率十分缓慢,对于几G的文件信息几乎难以使用RSA,因此需要先进行数字摘要获取较短的消息(如md5的128bit)后使用RSA处理。

JAVA中实现

但是在java中,发现数字签名算法并不是简单的进行数字摘要后再进行RSA加密的操作,这里记录一下,。

数字签名整体实现

1、首先需要获取一个RSA的公私钥对,可以从网站生成:在线生成公钥私钥对,RSA公私钥生成-ME2在线工具,注意java里的密钥格式只支持PKCS#8

2、代码实现:

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
String message = "hello world!";

String string_publickey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl1fMaxLw1PP6DYnmjNDJ" +
"/xc2a1siGI9lvzAML/nKTLwKMTgUsYRIFBGetlQqihZLwIi4FHwFrsB4K7Q1wBLj" +
"a9juUnQj603K9E8JGHK/BQbo4qkPIq4bYiJoPefYmY/xZuN34tpLcgd+M0L7wdnZ" +
"sjAXyesh2/Ofz24a3y+EcVlamsl090jUTVVFDUByLxpGhFmLEzKip8114Y5ArWoV" +
"kr46SLwZFvdOlcNiKJRhyG4nv7/6c4lzKQ0PFf1qBalZjoBOCqqtQBk8csuy0ujS" +
"wKNQCxZ7SxkWXNggeH2V3F6FWmcrdBSNB3XgY4/P2UsyEvxUXSvbTEJ3JPYMVGxK" +
"QQIDAQAB";

String string_privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCXV8xrEvDU8/oN" +
"ieaM0Mn/FzZrWyIYj2W/MAwv+cpMvAoxOBSxhEgUEZ62VCqKFkvAiLgUfAWuwHgr" +
"tDXAEuNr2O5SdCPrTcr0TwkYcr8FBujiqQ8irhtiImg959iZj/Fm43fi2ktyB34z" +
"QvvB2dmyMBfJ6yHb85/PbhrfL4RxWVqayXT3SNRNVUUNQHIvGkaEWYsTMqKnzXXh" +
"jkCtahWSvjpIvBkW906Vw2IolGHIbie/v/pziXMpDQ8V/WoFqVmOgE4Kqq1AGTxy" +
"y7LS6NLAo1ALFntLGRZc2CB4fZXcXoVaZyt0FI0HdeBjj8/ZSzIS/FRdK9tMQnck" +
"9gxUbEpBAgMBAAECggEAJhXxuvZixex5/mM4MjKuyrOgPccK3wxjKghg4MfnKYVe" +
"uUEoL/4el3JRsweeRu7SdGrTjFAjq/UCqNjqd+Io+jB21SgmC+ahTvb+zni96nlx" +
"LO0ngm43NOx8uCt6vjZTpTVUK7IMr28ESskuCvwURTvGGMnD1RLLoktOluDh3xYe" +
"0dAwtnvYwV/ptVWKESjLwI7vpyNzdWw8zyDeHnEf1Km/9vfXfZFlkfcHiHE9ZBFL" +
"Fs9cUeA1GE90sWh6XNroDphczKTBtHq7DIEvkB0A2q1LZK0t26Bp8iX8OKSg/NZr" +
"xKxUNJpM4PEE/8Jn3OGUTd431FE0d5U1GVEsfBKQEwKBgQDK/DT+1zmR4eEVi7yv" +
"FVvW3OJzjq8YLICQYxWeOYSO7qp48qzMKeFcOMH9fZBp3KLD1xiMZw7dhXIgKwWM" +
"PVv8gTUQGBURljMMTPNq9ntHV3Sdvu4SlqyElMjUZ8iDFZ781cDlXn5qVACfqT3i" +
"nH7BxOhjv4rscWDhB3fiRAcHlwKBgQC+3r2mMd7Tkn1VqdZpYQ4W+GUjHI9cDoKL" +
"nWGGo89TMCf0lFCIgHKSx5pHfJmQ+RgbOXaiQQdjDUuNerOClZRHLl1/a5BJSpoB" +
"MINBu57l3xowx9huJ/WdKIYJSWeNrIhXqRoWOvy04qmiKgTxQtRx/W6SLZa9AD+K" +
"G8AHMoU35wKBgQCew/2/hh/lVsNy0sVWPGKFAGeu8t9JDSXRyW5TP7HLCioUR9uY" +
"JPm75Dj3SIr4Ajbc7xSoXMNnyMSnHH9OXX/hVD5oxC1WgCCVcmrCMt/hwSnZaElW" +
"CLwPR0B9wl83CGY+ZAJoZSyDhgim2P0qjjUm4hjSTDMhFN0tpWXDeGn3KQKBgDuf" +
"aK6ZtXlxsSUpQIj6gGQ8g5tMOJwRDEaAiw577S+XQ2PrUJzTQvcG0vKxwWa/6P/Y" +
"G27j0WLnrEotohWQlBkIKry65P+ABkuI0/ecnC5CWvQR+VGvudG17ujHvUlMM+ru" +
"L+/xzS7T/xlg9tpGfgZ9nCSdiRDRGwgRUxLsfSDRAoGBAJNAoT3fZWP+i8Au3sWk" +
"fjxQZgSoMHZU4u1Vy5i7KnVPp4qIxZcIpz7RIex4XcjyYGVJnwyHoKwAKoMqjKd5" +
"87EJvvznCNq9RbJX4wpEooTQ7Rq6Me3H18XO+/s6M8IpKANDK+tYIVFJ8qjiYAdb" +
"afCUvJeq6jh3W4t0Iz/oLdw7";

//声明一个密钥工厂用来产生PublicKey和PrivateKey对象。
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] publickeyBytes = Base64.getDecoder().decode(string_publickey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publickeyBytes);
PublicKey publicKey = keyFactory.generatePublic(keySpec);

byte[] privatekeyBytes = Base64.getDecoder().decode(string_privateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privatekeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

// 签名
String signature = sign(message, privateKey);

// 验证
boolean isVerified = verify(message, signature, publicKey);

System.out.println("Message: " + message);
System.out.println("Signature: " + signature);
System.out.println("Verification result: " + isVerified);
System.out.println("-----------------------------------");

其中,signverify代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static String sign(String messageHash, PrivateKey privateKey) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(messageHash.getBytes());
byte[] signedBytes = signature.sign();
return Base64.getEncoder().encodeToString(signedBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static boolean verify(String messageHash, String signature, PublicKey publicKey) {
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(messageHash.getBytes());
return sig.verify(Base64.getDecoder().decode(signature));
} catch (Exception e) {
throw new RuntimeException(e);
}
}

输出结果如下:

1
2
3
4
Message: hello world!
Signature: aZuH+iI4ERsAQyH5RB/JFR/v8ip1c+iJZhGCqj8a78PeYCz3q72FNbjqpXm22xkTAAOrTihF5+EQgjE7yp71ocUX/8obHDtP1UZLwYx6LE7zAF0QCeDCiIbARC1VmbHoFGzNI4PIxdPBNQ9aTtFoCsChE3OMoY6Gda370SrCmki9zb0ognVjXFgSUct0AxhstRlAvPkTaf+5vNDvgp6iDavh5OONMbq27Z9gNoi4w8r+KbEq7O8vJ3X5Q3yG2ArUpJlp0uwpUNgSCvuEUb9Mctl6tgMIaZ198GDsUGpARjL37+Qs4qQhUxPu3LAg2KTt6F+dIwtRJrGP1SD5TC1Wyg==
Verification result: true
-----------------------------------

SHA256&RSA分开实现

接下来我们对明文消息使用SHA256和RSA逐步处理:

代码如下:

1
2
3
4
5
6
7
8
9
10
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(message.getBytes());
byte[] digestResult = sha256.digest();
String hex_digestResult = HexBin.encode(digestResult);
String base64_digestResult = Base64.getEncoder().encodeToString(digestResult);

Cipher cipher = Cipher.getInstance("RSA/ECB/NOPadding");
cipher.init(Cipher.ENCRYPT_MODE,privateKey);
byte[] bt = cipher.doFinal(digestResult);
System.out.println("Signature_base64: " + Base64.getEncoder().encodeToString(bt));

输出结果如下:

1
Signature: CbS16KUwv38eaxeD/OPFBVcCYWSUwlZcGdlMp/5DpBEbXUEjW3z17pW4ee+EuxJlkk8BmGr/y4X8efgq0ejSAhhuSH0k3eeZptae9HaBoNm3rGtJtqboUq8u5rlFtl1OCSvJDPiXTE4tbM1hezBxTR+O8KU/qgRBXfURx+SRIQ+BJQDpUy7JY7B2sfoihynt6pyKMJiUPU9mLGy2fHXD2gjzJPIgO6/lsemU7x9H6wd/6bzolgvdkMvXBccucfdV/pnvPcNUtGP36KpTrnUE+CaomJvlwZAdJQ5JRw1c94dlLh24i9+d0BFA5yZd4DrSh5VwvM4bwiWEfeH6ACTGrA==

发现整体实现和分开实现的结果并不一致!

差异分析

由于俩者结果并不一致,所以我们猜测上述的数字签名算法并不只是单纯的SHA256加RSA。

1、首先对数字签名生成的base64密文使用RSA公钥解密处理。

1
2
3
4
5
6
7
8
9
10
11
12
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(message.getBytes());
byte[] digestResult = sha256.digest();
String hex_digestResult = HexBin.encode(digestResult);
System.out.println("hex: " + hex_digestResult);

digestResult = Base64.getDecoder().decode("aZuH+iI4ERsAQyH5RB/JFR/v8ip1c+iJZhGCqj8a78PeYCz3q72FNbjqpXm22xkTAAOrTihF5+EQgjE7yp71ocUX/8obHDtP1UZLwYx6LE7zAF0QCeDCiIbARC1VmbHoFGzNI4PIxdPBNQ9aTtFoCsChE3OMoY6Gda370SrCmki9zb0ognVjXFgSUct0AxhstRlAvPkTaf+5vNDvgp6iDavh5OONMbq27Z9gNoi4w8r+KbEq7O8vJ3X5Q3yG2ArUpJlp0uwpUNgSCvuEUb9Mctl6tgMIaZ198GDsUGpARjL37+Qs4qQhUxPu3LAg2KTt6F+dIwtRJrGP1SD5TC1Wyg==");

Cipher cipher = Cipher.getInstance("RSA/ECB/NOPadding");
cipher.init(Cipher.DECRYPT_MODE,publicKey);
byte[] bt = cipher.doFinal(digestResult);
System.out.println("Signature: " + HexBin.encode(bt));

这里我们使用hex转换。输出结果如下:

1
2
hex: 7509E5BDA0C762D2BAC7F90D758B5B2263FA01CCBC542AB5E3DF163BE08E6CA9
Signature: 0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D0609608648016503040201050004207509E5BDA0C762D2BAC7F90D758B5B2263FA01CCBC542AB5E3DF163BE08E6CA9

可以发现,我们明文消息进行sha256加密后的hex值正好是Signature的后半段,且前半段是固定的。

所以得出结论:

java中的数字签名代码是将明文消息进行数字摘要处理后对数字摘要的hex在前边进行拼接固定字符串,之后取拼接后的字符串再进行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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
String message = "hello world!";

String string_publickey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl1fMaxLw1PP6DYnmjNDJ" +
"/xc2a1siGI9lvzAML/nKTLwKMTgUsYRIFBGetlQqihZLwIi4FHwFrsB4K7Q1wBLj" +
"a9juUnQj603K9E8JGHK/BQbo4qkPIq4bYiJoPefYmY/xZuN34tpLcgd+M0L7wdnZ" +
"sjAXyesh2/Ofz24a3y+EcVlamsl090jUTVVFDUByLxpGhFmLEzKip8114Y5ArWoV" +
"kr46SLwZFvdOlcNiKJRhyG4nv7/6c4lzKQ0PFf1qBalZjoBOCqqtQBk8csuy0ujS" +
"wKNQCxZ7SxkWXNggeH2V3F6FWmcrdBSNB3XgY4/P2UsyEvxUXSvbTEJ3JPYMVGxK" +
"QQIDAQAB";

String string_privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCXV8xrEvDU8/oN" +
"ieaM0Mn/FzZrWyIYj2W/MAwv+cpMvAoxOBSxhEgUEZ62VCqKFkvAiLgUfAWuwHgr" +
"tDXAEuNr2O5SdCPrTcr0TwkYcr8FBujiqQ8irhtiImg959iZj/Fm43fi2ktyB34z" +
"QvvB2dmyMBfJ6yHb85/PbhrfL4RxWVqayXT3SNRNVUUNQHIvGkaEWYsTMqKnzXXh" +
"jkCtahWSvjpIvBkW906Vw2IolGHIbie/v/pziXMpDQ8V/WoFqVmOgE4Kqq1AGTxy" +
"y7LS6NLAo1ALFntLGRZc2CB4fZXcXoVaZyt0FI0HdeBjj8/ZSzIS/FRdK9tMQnck" +
"9gxUbEpBAgMBAAECggEAJhXxuvZixex5/mM4MjKuyrOgPccK3wxjKghg4MfnKYVe" +
"uUEoL/4el3JRsweeRu7SdGrTjFAjq/UCqNjqd+Io+jB21SgmC+ahTvb+zni96nlx" +
"LO0ngm43NOx8uCt6vjZTpTVUK7IMr28ESskuCvwURTvGGMnD1RLLoktOluDh3xYe" +
"0dAwtnvYwV/ptVWKESjLwI7vpyNzdWw8zyDeHnEf1Km/9vfXfZFlkfcHiHE9ZBFL" +
"Fs9cUeA1GE90sWh6XNroDphczKTBtHq7DIEvkB0A2q1LZK0t26Bp8iX8OKSg/NZr" +
"xKxUNJpM4PEE/8Jn3OGUTd431FE0d5U1GVEsfBKQEwKBgQDK/DT+1zmR4eEVi7yv" +
"FVvW3OJzjq8YLICQYxWeOYSO7qp48qzMKeFcOMH9fZBp3KLD1xiMZw7dhXIgKwWM" +
"PVv8gTUQGBURljMMTPNq9ntHV3Sdvu4SlqyElMjUZ8iDFZ781cDlXn5qVACfqT3i" +
"nH7BxOhjv4rscWDhB3fiRAcHlwKBgQC+3r2mMd7Tkn1VqdZpYQ4W+GUjHI9cDoKL" +
"nWGGo89TMCf0lFCIgHKSx5pHfJmQ+RgbOXaiQQdjDUuNerOClZRHLl1/a5BJSpoB" +
"MINBu57l3xowx9huJ/WdKIYJSWeNrIhXqRoWOvy04qmiKgTxQtRx/W6SLZa9AD+K" +
"G8AHMoU35wKBgQCew/2/hh/lVsNy0sVWPGKFAGeu8t9JDSXRyW5TP7HLCioUR9uY" +
"JPm75Dj3SIr4Ajbc7xSoXMNnyMSnHH9OXX/hVD5oxC1WgCCVcmrCMt/hwSnZaElW" +
"CLwPR0B9wl83CGY+ZAJoZSyDhgim2P0qjjUm4hjSTDMhFN0tpWXDeGn3KQKBgDuf" +
"aK6ZtXlxsSUpQIj6gGQ8g5tMOJwRDEaAiw577S+XQ2PrUJzTQvcG0vKxwWa/6P/Y" +
"G27j0WLnrEotohWQlBkIKry65P+ABkuI0/ecnC5CWvQR+VGvudG17ujHvUlMM+ru" +
"L+/xzS7T/xlg9tpGfgZ9nCSdiRDRGwgRUxLsfSDRAoGBAJNAoT3fZWP+i8Au3sWk" +
"fjxQZgSoMHZU4u1Vy5i7KnVPp4qIxZcIpz7RIex4XcjyYGVJnwyHoKwAKoMqjKd5" +
"87EJvvznCNq9RbJX4wpEooTQ7Rq6Me3H18XO+/s6M8IpKANDK+tYIVFJ8qjiYAdb" +
"afCUvJeq6jh3W4t0Iz/oLdw7";

//声明一个密钥工厂用来产生PublicKey和PrivateKey对象。
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] publickeyBytes = Base64.getDecoder().decode(string_publickey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publickeyBytes);
PublicKey publicKey = keyFactory.generatePublic(keySpec);

byte[] privatekeyBytes = Base64.getDecoder().decode(string_privateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privatekeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

// 签名
String signature = sign(message, privateKey);
// 验证
boolean isVerified = verify(message, signature, publicKey);

System.out.println("Message: " + message);
System.out.println("Signature: " + signature);
System.out.println("Verification result: " + isVerified);
System.out.println("-----------------------------------");

MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(message.getBytes());
byte[] digestResult = sha256.digest();
String hex_digestResult = HexBin.encode(digestResult);
String temp = "0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420" + hex_digestResult; //拼接操作
Cipher cipher = Cipher.getInstance("RSA/ECB/NOPadding");
cipher.init(Cipher.ENCRYPT_MODE,privateKey);
byte[] bt = cipher.doFinal(HexBin.decode(temp));
System.out.println("Signature: " + Base64.getEncoder().encodeToString(bt));

输出结果如下:

1
2
3
4
5
Message: hello world!
Signature: aZuH+iI4ERsAQyH5RB/JFR/v8ip1c+iJZhGCqj8a78PeYCz3q72FNbjqpXm22xkTAAOrTihF5+EQgjE7yp71ocUX/8obHDtP1UZLwYx6LE7zAF0QCeDCiIbARC1VmbHoFGzNI4PIxdPBNQ9aTtFoCsChE3OMoY6Gda370SrCmki9zb0ognVjXFgSUct0AxhstRlAvPkTaf+5vNDvgp6iDavh5OONMbq27Z9gNoi4w8r+KbEq7O8vJ3X5Q3yG2ArUpJlp0uwpUNgSCvuEUb9Mctl6tgMIaZ198GDsUGpARjL37+Qs4qQhUxPu3LAg2KTt6F+dIwtRJrGP1SD5TC1Wyg==
Verification result: true
-----------------------------------
Signature: aZuH+iI4ERsAQyH5RB/JFR/v8ip1c+iJZhGCqj8a78PeYCz3q72FNbjqpXm22xkTAAOrTihF5+EQgjE7yp71ocUX/8obHDtP1UZLwYx6LE7zAF0QCeDCiIbARC1VmbHoFGzNI4PIxdPBNQ9aTtFoCsChE3OMoY6Gda370SrCmki9zb0ognVjXFgSUct0AxhstRlAvPkTaf+5vNDvgp6iDavh5OONMbq27Z9gNoi4w8r+KbEq7O8vJ3X5Q3yG2ArUpJlp0uwpUNgSCvuEUb9Mctl6tgMIaZ198GDsUGpARjL37+Qs4qQhUxPu3LAg2KTt6F+dIwtRJrGP1SD5TC1Wyg==

此时俩者结果一致!

安卓逆向Hook通杀

没什么好写的。。

因为调用的是java层标准加密库,直接hook对于函数即可。

1、MessageDigest类

hook update或digest可以获得明文。

2、Ciper类

hook init可以获得密钥和IV(CBC模式下)。

hook dofinal可以获得密文或明文。

3、Signature类

hook update可以获得密文或明文。

hook initsign利用获得私钥。

hook initverify可以获得公钥。