RSA Public-Key Encryption and Signature Lab
RSA(RIVEST-Shamir-Adleman)是第一個公鑰密碼系統之一,廣泛用於安全通信。 RSA算法將生成兩個大的隨機素數,然后使用它們生成公鑰和私鑰對,該對可用於進行加密,解密,數字簽名生成和數字簽名驗證。 RSA算法建立在數字理論上,並且可以通過庫的支持很容易地實現。
C語言部分作者:對酒當歌
該實驗的學習目標是為學生獲得RSA算法的實踐經驗。我們應該學習了RSA算法的理論部分,因此他們可以聞名地知道如何生成公鑰/私鑰以及如何執行加密/解密和簽名生成/驗證。該實驗室通過要求他們通過RSA算法的實際數字的每個重要步驟增強了學生對RSA的理解,因此他們可以應用從類中學到的理論。基本上,學生將使用C程序語言實現RSA算法。實驗室涵蓋以下與安全相關主題:
- 公鑰加密
- RSA算法和鍵生成
- 大數字計算
- 使用RSA加密和解密
- 電子簽名
- X.509認證
讀數和視頻。 公鑰加密的詳細覆蓋范圍可以在以下內容中找到:
- 種子書,計算機和互聯網安全的第23章:Wenliang du的動手方法,第2版。 查看https://www.handsonsecurity.net的詳細信息。
實驗室環境: 此實驗室已在我們預構建的 Ubuntu 16.04 VM 上進行了測試,該 VM 可以從 SEED 網站下載。 本練習需要 openssllibrary,該庫已安裝在 Ubuntu16.04 VM 上。如果選擇使用其他 VM,則可以運行以下命令來安裝 openssl,眾所周知,seed12.04是安裝不了任何東西的,我們可以隨機選虛擬機
$ sudo apt-get update
$ sudo apt-get install libssl-dev
RSA簡介
RSA算法涉及大量計算。這些計算無法使用程序中的簡單算術運算符直接控制,因為這些操作員只能在原始數據類型上運行,例如32位整數和64位長整數類型。 RSA算法涉及的數字通常大於512位。例如,到多個兩個32位整數A和B,我們只需要在我們的程序中使用* b。但是,如果他們是大數字,我們就不能再這樣做了;相反,我們需要使用算法(即,函數)來計算其產品。
有幾個庫可以在任意大小的整數上執行算術運算。在此實驗室中,我們將使用OpenSSL提供的大數字庫。要使用此庫,我們將將每個大數字作為Bignum類型,然后使用庫提供的API用於各種操作,例如添加,乘法,指數,模塊化操作等。
公鑰加密技術:
- 是當今安全通信的基礎
- 允許通信雙方獲取共享密鑰
- 公鑰(用於加密)和私鑰(用於解密)
- 私鑰(用於數字簽名)和公鑰(用於驗證簽名)
RSA
-
需要生成:模數n,公鑰指數e,私鑰指數d
-
方法
- 選擇p,q(大型隨機質數)
- N = pq(應該很大)
- 選擇e, 1 < e < φ(n) e相對於φ(n)是質數
- 求\(d, ed \mod φ(n) = 1\)
-
結果
- (e,n)為公鑰
- d 為私鑰
-
加密
將明文視為數字,假設M < n
\(C = M^e \mod n\) -
解密
\(M = C^d \mod n\)
證明
2.1 BIGNUM api
所有大量的api都可以從https://linux.die.net/man/3/bn找到。在下文中,我們將描述這個實驗室需要的一些api。
-
一些庫函數需要臨時變量。由於動態內存分配來創建BIGNUMs非常昂貴,當與重復子例程調用一起使用時,創建一個BN CTX結構來保存庫函數使用的BIGNUM臨時變量。我們需要創建這樣一個結構,並將其傳遞給需要它的函數。
BN_CTX *ctx = BN_CTX_new()
-
初始化BIGNUM變量
BIGNUM *a = BN_new()
-
有很多方法可以給BIGNUM變量賦值。
// 從十進制數字字符串中指定一個值 BN_dec2bn(&a, "12345678901112231223"); // 從十六進制數字字符串賦值 BN_hex2bn(&a, "2A3B4C55FF77889AED3F"); // 生成128位的隨機數 BN_rand(a, 128, 0, 0); // 生成一個128位的隨機素數 BN_generate_prime_ex(a, 128, 1, NULL, NULL, NULL);
-
打印一個大數
void printBN(char *msg, BIGNUM * a) { // Convert the BIGNUM to number string char * number_str = BN_bn2dec(a); // Print out the number string printf("%s %s\n", msg, number_str); // Free the dynamically allocated memory OPENSSL_free(number_str); }
-
計算 \(res = a - b\) 和 \(res = a + b\):
BN_sub(res, a, b); BN_add(res, a, b);
-
計算\(res = a \times b\)。應該注意的是,此 API 中需要 BN CTX 結構。
BN_mul(res, a, b, ctx)
- 計算\(res = a \times b \mod n\):
BN_mod_mul(res, a, b, n, ctx)
- 計算\(res = a^c \mod n\)
BN_mod_exp(res, a, c, n, ctx)
- 計算模逆,即給定 a,找到 b,使得 \(a \times b \mod n = 1\)。值 b 相對於模 n 稱為 a 的逆。
BN_mod_inverse(b, a, n, ctx);
2.2 完整示例
我們在下面展示了一個完整的示例。在這個例子中,我們初始化了三個BIGNUM變量,a,b和n;然后我們計算a * b和(\(a^b \mod n\))。
/ *bn_sample.c * /
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 256
void printBN(char *msg, BIGNUM *a)
{
/* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *a = BN_new();
BIGNUM *b = BN_new();
BIGNUM *n = BN_new();
BIGNUM *res = BN_new();
// Initialize a, b, n
BN_generate_prime_ex(a, NBITS, 1, NULL, NULL, NULL);
BN_dec2bn(&b, "273489463796838501848592769467194369268");
BN_rand(n, NBITS, 0, 0);
// res = a *b
BN_mul(res, a, b, ctx);
printBN("a * b = ", res);
// res = aˆb mod n
BN_mod_exp(res, a, b, n, ctx);
printBN("aˆc mod n = ", res);
return 0;
}
編譯我們可以使用以下命令編譯 bn sample.c(- 后面的字符是字母 l,而不是數字 1;它告訴編譯器使用加密庫)。
gcc bn_sample.c -lcrypto
3任務
為避免錯誤,請避免手動鍵入用於實驗室任務的數字。復制並粘貼其中的數字。
3.1 任務1:派生私鑰
設 p、q 和 e 為三個素數。設 \(n = p \times q\)。我們將使用(e,n)作為公鑰。請計算私鑰d。下面列出了 p、q 和 e 的十六進制值。應該注意的是,盡管此任務中使用的p和q是相當大的數字,但它們不夠大而不安全。為了簡單起見,我們故意使它們變小。在實踐中,這些數字的長度應至少為 512 位(此處使用的數字僅為 128 位)。
p = F7E75FDC469067FFDC4E847C51F452DF q = E85CED54AF57E53E092113E62F436F4F e = 0D88C3
編寫程序C
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *p = BN_new();
BIGNUM *q = BN_new();
BIGNUM *fai_n = BN_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *d = BN_new();
BIGNUM *p_1 = BN_new();
BIGNUM *q_1 = BN_new();
BN_hex2bn(&p, "F7E75FDC469067FFDC4E847C51F452DF");
BN_hex2bn(&q, "E85CED54AF57E53E092113E62F436F4F");
BN_hex2bn(&e, "0D88C3");
BN_sub(p_1, p, BN_value_one());
BN_sub(q_1, q, BN_value_one());
BN_mul(n, p, q, ctx);
BN_mul(fai_n, p_1, q_1, ctx);
//printBN("fai_n=", fai_n);
BN_mod_inverse(d, e, fai_n, ctx);
printBN("public key e=\t", e);
printBN("public key n=\t", n);
printBN("private key d=\t", d);
return 0;
}
gcc t1.c -lcrypto -o t1
t1
求得:
public key e= 0D88C3
public key n= E103ABD94892E3E74AFD724BF28E78366D9676BCCC70118BD0AA1968DBB143D1
private key d= 3587A24598E5F2A21DB007D89D18CC50ABA5075BA19A33890FE7C28A9B496AEB
python版本
import gmpy2
p = 0xF7E75FDC469067FFDC4E847C51F452DF
q = 0xE85CED54AF57E53E092113E62F436F4F
e = 0x0D88C3
n = q * p
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
print("public key:(" + hex(e) + ',' + hex(n) + ")")
print("private key:" + hex(d))
3.2 任務 2:加密消息
設 (e, n) 為公鑰。請加密消息“A top secret!”引文不包括在內)。我們需要將此 ASCII 字符串轉換為十六進制字符串,然后使用 hex-to-bn API BN hex2bn() 將十六進制字符串轉換為 BIGNUM。以下 python 命令可用於將普通 ASCII 字符串轉換為十六進制字符串。
$ python -c ' print("A top secret!".encode("hex")) ') ' 4120746f702073656372657421
公鑰如下所示(十六進制)。我們還提供私鑰d,以幫助您驗證您的加密結果。
n = DCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5 e = 010001 (this hex value equals to decimal 65537) M = A top secret! d = 74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D
編寫程序
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *d = BN_new();
BIGNUM *m = BN_new(); //massage
//BIGNUM* p = BN_new(); //plaintxt
BIGNUM *c = BN_new(); //cyphertxt
BN_hex2bn(&n, "DCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5");
BN_hex2bn(&e, "010001");
BN_hex2bn(&d, "74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D");
BN_hex2bn(&m, "4120746f702073656372657421");
BN_mod_exp(c, m, e, n, ctx);
//BN_mod_exp(p, c, d, n, ctx);
printBN("cyphertxt:", c);
//printBN("plaintxt:", p);
return 0;
}
$ gcc t2.c -lcrypto -o t2
t2
加密結果為:6FB078DA550B2650832661E14F4F8D2CFAEF475A0DF3A75CACDC5DE5CFC5FADC
python程序
import gmpy2
n = 0xDCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5
e = 0x010001
m = "A top secret!".encode("utf-8").hex()
m = int(m, 16)
c = gmpy2.powmod(m, e, n)
print(hex(c))
3.3任務3:解密消息
此任務中使用的公鑰/私鑰與task 2中使用的公鑰/私鑰相同。請解密以下密文C,並將其轉換為純ASCII字符串。
C = 8C0F971DF2F3672B28811407E2DABBE1DA0FEBBBDFC7DCB67396567EA1E2493F
您可以使用以下python命令將十六進制字符串轉換為普通ASCII字符串。
$ python -c 'print("4120746f702073656372657421".decode("hex"))' A top secret!
編寫程序C
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *d = BN_new();
BIGNUM *p = BN_new(); //plaintxt
BIGNUM *c = BN_new(); //cyphertxt
BN_hex2bn(&n, "DCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5");
BN_hex2bn(&e, "010001");
BN_hex2bn(&d, "74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D");
BN_hex2bn(&c, "8C0F971DF2F3672B28811407E2DABBE1DA0FEBBBDFC7DCB67396567EA1E2493F");
BN_mod_exp(p, c, d, n, ctx);
printBN("plaintxt:", p);
return 0;
}
$ gcc t3.c -lcrypto -o t3
t3
解密結果為:50617373776F72642069732064656573
使用python將其轉換成ASCII字符串:
# python2 seed12 seed16
python2 -c 'print("50617373776F72642069732064656573".decode("hex"))'
# python3 seed20
python3 -c 'print(bytes.fromhex("50617373776F72642069732064656573").decode("utf-8"))'
python程序
import gmpy2
n = 0xDCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5
d = 0x74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D
C = 0x8C0F971DF2F3672B28811407E2DABBE1DA0FEBBBDFC7DCB67396567EA1E2493F
M = gmpy2.powmod(C, d, n)
m = str(hex(M))[2:]
print("M:" + bytes.fromhex(m).decode("utf-8"))
3.4任務4:簽名
通過簽署數字文件提供真實性證明
在M使用私鑰對M應用私鑰操作,並獲得一個數字,每個人都可以使用我們的公鑰來從s回來對於需要簽名的消息:
\(數字簽名= m^d \mod n\)
在實踐中,消息可能很長,從而產生長簽名和更多的計算時間,因此,我們從原始信息中生成加密哈希值,並且只簽署哈希值此任務中使用的公鑰/私鑰與task 2中使用的公鑰/私鑰相同。請為以下消息生成簽名(請直接簽名此消息,而不是簽名其哈希值):
M = I owe you $2000.
請對M消息做一點小小的修改,比如把$2000改成$3000,並在修改后的消息上簽名。比較兩個簽名並描述你所觀察到的。
M = I owe you $3000.
將兩個簽名轉換
# python3 seed12 seed16
python -c 'print("I owe you $2000".encode("hex"))'
python -c 'print("I owe you $3000".encode("hex"))'
# python3 seed20
"I owe you $2000".encode("utf-8").hex()
"I owe you $3000".encode("utf-8").hex()
或
import binascii
binascii.hexlify(b"I owe you $2000")
binascii.hexlify(b"I owe you $3000")
得到:
49206f776520796f75202432303030
49206f776520796f75202433303030
編寫程序C
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *d = BN_new();
BIGNUM *m1 = BN_new();
BIGNUM *m2 = BN_new();
BIGNUM *sig1 = BN_new();
BIGNUM *sig2 = BN_new();
BN_hex2bn(&n, "DCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5");
BN_hex2bn(&e, "010001");
BN_hex2bn(&d, "74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D");
BN_hex2bn(&m1, "49206f776520796f75202432303030");
BN_hex2bn(&m2, "49206f776520796f75202433303030");
BN_mod_exp(sig1, m1, d, n, ctx);
BN_mod_exp(sig2, m2, d, n, ctx);
printBN("signature of m1:", sig1);
printBN("signature of m2:", sig2);
return 0;
}
gcc t4.c -lcrypto -o t4
t4
signature of m1: 80A55421D72345AC199836F60D51DC9594E2BDB4AE20C804823FB71660DE7B82
signature of m2: 04FC9C53ED7BBE4ED4BE2C24B0BDF7184B96290B4ED4E3959F58E94B1ECEA2EB
可見,雖然信息只做了稍微的改動,簽名結果也會發生很大的變化。
python程序
import gmpy2
n = 0xDCBFFE3E51F62E09CE7032E2677A78946A849DC4CDDE3A4D0CB81629242FB1A5
e = 0x010001
d = 0x74D806F9F3A62BAE331FFE3F0A68AFE35B3D2E4794148AACBC26AA381CD7D30D
msg = ["I owe you $2000", "I owe you $3000"]
for m in msg:
print(m)
m = int(m.encode("utf-8").hex(), 16)
c = gmpy2.powmod(m, d, n)
print(hex(c))
3.5任務5:驗證簽名
Bob收到來自Alice的消息M = "Launch a missile.",其簽名為s。我們知道Alice的公鑰是(e, n),請驗證該簽名是否確實是Alice的。公鑰和簽名(十六進制)如下所示:
M = Launch a missile. S = 643D6F34902D9C7EC90CB0B2BCA36C47FA37165C0005CAB026C0542CBDB6802F e = 010001 (this hex value equals to decimal 65537) n = AE1CD4DC432798D933779FBD46C6E1247F0CF1233595113AA51B450F18116115
假設in的簽名已損壞,簽名的最后一個字節從2F更改為3F,即只有一個比特的更改。請重復這個任務,並描述驗證過程會發生什么。
編寫程序C
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *M = BN_new();
BIGNUM *m1 = BN_new();
BIGNUM *m2 = BN_new();
BIGNUM *sig1 = BN_new();
BIGNUM *sig2 = BN_new();
%TRHrnT#ARGXB
BN_hex2bn(&M, "4c61756e63682061206d697373696c652e");
BN_hex2bn(&n, "AE1CD4DC432798D933779FBD46C6E1247F0CF1233595113AA51B450F18116115");
BN_hex2bn(&e, "010001");
BN_hex2bn(&sig1, "643D6F34902D9C7EC90CB0B2BCA36C47FA37165C0005CAB026C0542CBDB6802F");
BN_hex2bn(&sig2, "643D6F34902D9C7EC90CB0B2BCA36C47FA37165C0005CAB026C0542CBDB6803F");
BN_mod_exp(m1, sig1, e, n, ctx);
BN_mod_exp(m2, sig2, e, n, ctx);
printf("verifying of signature1:");
if (BN_cmp(m1, M) == 0)
{
printf("valid!\n");
}
else
{
printf("invalid!\n");
}
printf("verifying of signature2:");
if (BN_cmp(m2, M) == 0)
{
printf("valid!\n");
}
else
{
printf("invalid!\n");
}
return 0;
}
gcc t5.c -lcrypto -o t5
t5
當簽名沒有損壞時,可以驗證成功。即使簽名只變動了一位,也會使得驗證失敗。
python程序
import gmpy2
M = "Launch a missile."
Sorg = 0x643D6F34902D9C7EC90CB0B2BCA36C47FA37165C0005CAB026C0542CBDB6802F
Serr = 0x643D6F34902D9C7EC90CB0B2BCA36C47FA37165C0005CAB026C0542CBDB6802A
n = 0xAE1CD4DC432798D933779FBD46C6E1247F0CF1233595113AA51B450F18116115
e = 0x010001
sig = [Sorg, Serr]
for S in sig:
m = gmpy2.powmod(S, e, n)
m = str(hex(m))[2:]
try:
msg = bytes.fromhex(m).decode('utf-8')
print(msg)
if m == M:
print("signature valid!")
else:
print("signature invalid!")
except:
print(bytes.fromhex(m))
print("signature invalid!")
3.6任務6:手動驗證X.509證書
在這個任務中,我們將使用我們的程序手動驗證X.509證書。509包含關於公鑰的數據和數據上的發布者簽名。我們將從web服務器下載一個真實的X.509證書,獲取其頒發者的公鑰,然后使用這個公鑰來驗證證書上的簽名。
步驟1:從真實的web服務器下載證書。
我們使用www.example.org服務器這個文檔。學生應該選擇擁有不同證書的不同的網絡服務器
本文檔中使用的證書(需要注意的是www.example.com與www.example.org共享相同的證書)。我們可以使用瀏覽器下載證書,也可以使用以下命令下載證書:$ openssl s_client -connect www.example.org:443 -showcerts Certificate chain 0 s:/C=US/ST=California/L=Los Angeles/O=Internet Corporation for Assigned Names and Numbers/OU=Technology/CN=www.example.org i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA -----BEGIN CERTIFICATE----- MIIF8jCCBNqgAwIBAgIQDmTF+8I2reFLFyrrQceMsDANBgkqhkiG9w0BAQsFADBw MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 ...... wDSiIIWIWJiJGbEeIO0TIFwEVWTOnbNl/faPXpk5IRXicapqiII= -----END CERTIFICATE----- 1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV Root CA -----BEGIN CERTIFICATE----- MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 ...... cPUeybQ= -----END CERTIFICATE-----
命令執行結果中包含兩個證書。證書的主題字段(以s:開頭的條目)是www.example.org,也就是說,這是www.example.org的證書。issuer字段(以i:開頭的條目)提供了發行者的信息。第二個證書的主題字段與第一個證書的頒發者字段相同。基本上,第二個證書屬於中間CA。在本任務中,我們將使用CA的證書來驗證服務器證書。
如果您使用上面的命令只能獲得一個證書,這意味着您獲得的證書是由根CA簽名的。根CA的證書可以從安裝在我們預構建的虛擬機中的Firefox瀏覽器中獲得。點擊編輯→首選項→隱私,然后點擊安全→查看證書。請搜索頒發者名稱並下載其證書。
將每個證書(包含“Begin certificate”行和包含“END certificate”行之間的文本,包括這兩行)復制並粘貼到一個文件中。我們稱第一個為c0.pem和第二個c1.pem。
openssl s_client -connect www.example.org:443 -showcerts
得到如下結果:
將第一個證書復制到文件:c0.pem,無需調整換行,帶上-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----
將第二個證書復制到文件:c1.pem
為了避免格式問題,盡量在seed中編輯文件
步驟2:從頒發者的證書中提取公鑰(e , n)。
Openssl提供了從x509證書中提取某些屬性的命令。我們可以用-模求出n的值。沒有特定的命令來提取e,但是我們可以打印出所有的字段,並且可以很容易地找到e的值。
For modulus (n): $ openssl x509 -in c1.pem -noout -modulus Print out all the fields, find the exponent (e): $ openssl x509 -in c1.pem -text -noout
使用命令得到公鑰的模數n:
openssl x509 -in c1.pem -noout -modulus
得到n=C14BB3654770BCDD4F58DBEC9CEDC366E51F311354AD4A66461F2C0AEC6407E52EDCDCB90A20EDDFE3C4D09E9AA97A1D8288E51156DB1E9F58C251E72C340D2ED292E156CBF1795FB3BB87CA25037B9A52416610604F571349F0E8376783DFE7D34B674C2251A6DF0E9910ED57517426E27DC7CA622E131B7F238825536FC13458008B84FFF8BEA75849227B96ADA2889B15BCA07CDFE951A8D5B0ED37E236B4824B62B5499AECC767D6E33EF5E3D6125E44F1BF71427D58840380B18101FAF9CA32BBB48E278727C52B74D4A8D697DEC364F9CACE53A256BC78178E490329AEFB494FA415B9CEF25C19576D6B79A72BA2272013B5D03D40D321300793EA99F5
openssl x509 -in c1.pem -text -noout
得到,e=0x10001
步驟3:從服務器的證書中提取簽名。
沒有特定的openssl命令來提取簽名字段。但是,我們可以打印出所有的字段,然后復制並粘貼簽名
(注意:如果證書中使用的簽名算法不是基於RSA的,您可以找到另一個證書)。$ openssl x509 -in c0.pem -text -noout ... Signature Algorithm: sha256WithRSAEncryption 84:a8:9a:11:a7:d8:bd:0b:26:7e:52:24:7b:b2:55:9d:ea:30: 89:51:08:87:6f:a9:ed:10:ea:5b:3e:0b:c7:2d:47:04:4e:dd: ...... 5c:04:55:64:ce:9d:b3:65:fd:f6:8f:5e:99:39:21:15:e2:71: aa:6a:88:82
我們需要從數據中刪除空格和冒號,這樣就可以得到一個十六進制字符串,並將其輸入到程序中。下面的命令可以實現這個目標。tr命令是一個用於字符串操作的Linux實用工具。在本例中,-d選項用於刪除數據的":"和"space"。
$ cat signature | tr -d `[:space:]:` 84a89a11a7d8bd0b267e52247bb2559dea30895108876fa9ed10ea5b3e0bc7 ...... 5c045564ce9db365fdf68f5e99392115e271aa6a8882
使用命令找到簽名signature:
openssl x509 -in c0.pem -text -noout
將簽名復制到文件signature,刪除空格和冒號
cat signature | tr -d '[:space:]:'
得到signature:
aa9fbe5d911bade44e4ecc8f07644435b4ad3b133fc129d8b4abf3425149463bd6cf1e4183e10b572f83697965076f59038c51948918103e1e5cedba3d8e4f1a1492d32bffd498cba7930ebcb71b93a4424246d9e5b11a6b682a9b2e48a92f1d2ab0e3f820945481502eeed7e0207a7b2e67fbfad817a45bdcca0062ef23af7a58f07a740cbd4d43f18c0287dce3ae09d2f7fa373cd24bab04e543a5d255110e41875f38a8e57a5e4c46b8b6fa3fc34bcd4035ffe0a471740ac1208be3544784d518bd519b405ddd423012d13aa5639aaf9008d61bd1710b067190ebaeadafba5fc7db6b1e78a2b4d10623a763f3b543fa568c50177b1c1b4e106b220e845294
步驟4:提取服務器證書的主體
證書頒發機構(CA)首先計算證書的哈希值,然后對哈希值進行簽名,從而為服務器證書生成簽名。為了驗證簽名,我們還需要從證書生成散列。由於哈希是在計算簽名之前生成的,所以在計算哈希時需要排除證書的簽名塊。如果不能很好地理解證書的格式,那么要找出證書的哪一部分用於生成散列是非常困難的。
509證書是使用ASN.1(抽象語法符號1)標准編碼的,所以如果我們能解析ASN.1結構,我們就能很容易地從證書中提取任何字段。Openssl有一個名為asn1parse的命令,可以用來解析X.509證書。$ openssl asn1parse -i -in c0.pem 0:d=0 hl=4 l=1522 cons: SEQUENCE 4:d=1 hl=4 l=1242 cons: SEQUENCE ➊ 8:d=2 hl=2 l= 3 cons: cont [ 0 ] 10:d=3 hl=2 l= 1 prim: INTEGER :02 13:d=2 hl=2 l= 16 prim: INTEGER :0E64C5FBC236ADE14B172AEB41C78CB0 ... ... 1236:d=4 hl=2 l= 12 cons: SEQUENCE 1238:d=5 hl=2 l= 3 prim: OBJECT :X509v3 Basic Constraints 1243:d=5 hl=2 l= 1 prim: BOOLEAN :255 1246:d=5 hl=2 l= 2 prim: OCTET STRING [HEX DUMP]:3000 1250:d=1 hl=2 l= 13 cons: SEQUENCE ➋ 1252:d=2 hl=2 l= 9 prim: OBJECT :sha256WithRSAEncryption 1263:d=2 hl=2 l= 0 prim: NULL 1265:d=1 hl=4 l= 257 prim: BIT STRING
從➊開始的字段是用來生成哈希的證書的主體;從➋開始的字段為簽名塊。它們的偏移量是每行開頭的數字。在我們的例子中,證書主體是從偏移量4到1249,而簽名塊是從文件末尾的1250。對於X.509證書,起始偏移量總是相同的(即4),但結束偏移量取決於證書的內容長度。我們可以使用-strparse選項從偏移量4獲取字段,這將為我們提供證書的主體,不包括簽名塊。
$ openssl asn1parse -i -in c0.pem -strparse 4 -out c0_body.bin -noout
一旦我們獲得證書的主體,我們可以使用以下命令計算它的哈希值:
$ sha256sum c0_body.bin
使用命令解析服務器的證書:
openssl asn1parse -i -in c0.pem
用來生成哈希的證書的主體:
簽名塊:
得到證書的主體,並計算它的哈希值
哈希值為:
7061df0a50b8f2ba3367ecfabab273a16f3bb1378dbe1fe524e6dfd90dfa3b91
步驟5:驗證簽名。
現在我們擁有了所有信息,包括CA的公鑰、CA的簽名和服務器證書的主體。我們可以運行自己的程序來驗證簽名是否有效。Openssl確實為我們提供了一個驗證證書的命令,但要求學生使用自己的程序來驗證證書,否則,他們在這項任務中得到的分數為零。
#include <stdio.h>
#include <openssl/bn.h>
#define NBITS 128
void printBN(char *msg, BIGNUM *a)
{ /* Use BN_bn2hex(a) for hex string
* Use BN_bn2dec(a) for decimal string */
char *number_str = BN_bn2hex(a);
printf("%s %s\n", msg, number_str);
OPENSSL_free(number_str);
}
int main()
{
BN_CTX *ctx = BN_CTX_new();
BIGNUM *n = BN_new();
BIGNUM *e = BN_new();
BIGNUM *sig = BN_new();
BIGNUM *m = BN_new();
BN_hex2bn(&n, "C14BB3654770BCDD4F58DBEC9CEDC366E51F311354AD4A66461F2C0AEC6407E52EDCDCB90A20EDDFE3C4D09E9AA97A1D8288E51156DB1E9F58C251E72C340D2ED292E156CBF1795FB3BB87CA25037B9A52416610604F571349F0E8376783DFE7D34B674C2251A6DF0E9910ED57517426E27DC7CA622E131B7F238825536FC13458008B84FFF8BEA75849227B96ADA2889B15BCA07CDFE951A8D5B0ED37E236B4824B62B5499AECC767D6E33EF5E3D6125E44F1BF71427D58840380B18101FAF9CA32BBB48E278727C52B74D4A8D697DEC364F9CACE53A256BC78178E490329AEFB494FA415B9CEF25C19576D6B79A72BA2272013B5D03D40D321300793EA99F5");
BN_hex2bn(&e, "010001");
BN_hex2bn(&sig, "aa9fbe5d911bade44e4ecc8f07644435b4ad3b133fc129d8b4abf3425149463bd6cf1e4183e10b572f83697965076f59038c51948918103e1e5cedba3d8e4f1a1492d32bffd498cba7930ebcb71b93a4424246d9e5b11a6b682a9b2e48a92f1d2ab0e3f820945481502eeed7e0207a7b2e67fbfad817a45bdcca0062ef23af7a58f07a740cbd4d43f18c0287dce3ae09d2f7fa373cd24bab04e543a5d255110e41875f38a8e57a5e4c46b8b6fa3fc34bcd4035ffe0a471740ac1208be3544784d518bd519b405ddd423012d13aa5639aaf9008d61bd1710b067190ebaeadafba5fc7db6b1e78a2b4d10623a763f3b543fa568c50177b1c1b4e106b220e845294");
BN_mod_exp(m, sig, e, n, ctx);
printBN("message:", m);
return 0;
}
gcc t6.c -lcrypto -o t6
t6
得到,驗證結果為:
01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D0609608648016503040201050004207061DF0A50B8F2BA3367ECFABAB273A16F3BB1378DBE1FE524E6DFD90DFA3B91
根據sha256WithRSAEncryption可知,發放證書用到的hash算法是sha256,即得到的hash值共有256位,256/4=64。再根據於X.509證書簽名驗證的流程可知,只需要將上述結果的后64個十六進制數與step4得到的hash值做比較即可。
使用python實現:
m = "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D0609608648016503040201050004207061DF0A50B8F2BA3367ECFABAB273A16F3BB1378DBE1FE524E6DFD90DFA3B91"
hash = "7061df0a50b8f2ba3367ecfabab273a16f3bb1378dbe1fe524e6dfd90dfa3b91"
print(m[-64:].lower()==hash )
驗證成功,簽名有效!
python
import gmpy2
S = 0xaa9fbe5d911bade44e4ecc8f07644435b4ad3b133fc129d8b4abf3425149463bd6cf1e4183e10b572f83697965076f59038c51948918103e1e5cedba3d8e4f1a1492d32bffd498cba7930ebcb71b93a4424246d9e5b11a6b682a9b2e48a92f1d2ab0e3f820945481502eeed7e0207a7b2e67fbfad817a45bdcca0062ef23af7a58f07a740cbd4d43f18c0287dce3ae09d2f7fa373cd24bab04e543a5d255110e41875f38a8e57a5e4c46b8b6fa3fc34bcd4035ffe0a471740ac1208be3544784d518bd519b405ddd423012d13aa5639aaf9008d61bd1710b067190ebaeadafba5fc7db6b1e78a2b4d10623a763f3b543fa568c50177b1c1b4e106b220e845294
n = 0xC14BB3654770BCDD4F58DBEC9CEDC366E51F311354AD4A66461F2C0AEC6407E52EDCDCB90A20EDDFE3C4D09E9AA97A1D8288E51156DB1E9F58C251E72C340D2ED292E156CBF1795FB3BB87CA25037B9A52416610604F571349F0E8376783DFE7D34B674C2251A6DF0E9910ED57517426E27DC7CA622E131B7F238825536FC13458008B84FFF8BEA75849227B96ADA2889B15BCA07CDFE951A8D5B0ED37E236B4824B62B5499AECC767D6E33EF5E3D6125E44F1BF71427D58840380B18101FAF9CA32BBB48E278727C52B74D4A8D697DEC364F9CACE53A256BC78178E490329AEFB494FA415B9CEF25C19576D6B79A72BA2272013B5D03D40D321300793EA99F5
e = 0x010001
m = gmpy2.powmod(S, e, n)
hash = "7061df0a50b8f2ba3367ecfabab273a16f3bb1378dbe1fe524e6dfd90dfa3b91"
print(hex(m)[-64:].lower() == hash)