RSA Public-Key Encryption and Signature Lab seed solution


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\)

證明

\[M^{ed} =M^{k\phi(n)+1} \mod n \dots\dots\dots\dots\dots\dots\dots\dot\dots\\ = M^{k\phi(n)}*M \mod n\dots\dots\dots\dots\dots\dots\\ = (M^{\phi(n)}\mod n)^k\times M \mod n \text{(分配規則)}\\ =1^k \times M \mod n \text{(歐拉定理)}\dots\dots\dots\dots\dots \\ =M\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots \]

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

image-20220417185639827

求得:

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))

image-20220420195255107

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

image-20220417190059502

加密結果為: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

image-20220417190317750

解密結果為:50617373776F72642069732064656573

使用python將其轉換成ASCII字符串:

# python2 seed12 seed16
python2 -c 'print("50617373776F72642069732064656573".decode("hex"))'
# python3 seed20
python3 -c 'print(bytes.fromhex("50617373776F72642069732064656573").decode("utf-8"))'

image-20220417192356278

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:簽名

通過簽署數字文件提供真實性證明
image-20220420212527112

在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

image-20220417193618357

編寫程序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

image-20220417193920793

可見,雖然信息只做了稍微的改動,簽名結果也會發生很大的變化。

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

image-20220417194236398

當簽名沒有損壞時,可以驗證成功。即使簽名只變動了一位,也會使得驗證失敗。

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!")

image-20220420224226151

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

得到如下結果:

image-20220417194618253

將第一個證書復制到文件:c0.pem,無需調整換行,帶上-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----

將第二個證書復制到文件:c1.pem

為了避免格式問題,盡量在seed中編輯文件

image-20220417195818759

步驟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

image-20220417200506327

得到n=C14BB3654770BCDD4F58DBEC9CEDC366E51F311354AD4A66461F2C0AEC6407E52EDCDCB90A20EDDFE3C4D09E9AA97A1D8288E51156DB1E9F58C251E72C340D2ED292E156CBF1795FB3BB87CA25037B9A52416610604F571349F0E8376783DFE7D34B674C2251A6DF0E9910ED57517426E27DC7CA622E131B7F238825536FC13458008B84FFF8BEA75849227B96ADA2889B15BCA07CDFE951A8D5B0ED37E236B4824B62B5499AECC767D6E33EF5E3D6125E44F1BF71427D58840380B18101FAF9CA32BBB48E278727C52B74D4A8D697DEC364F9CACE53A256BC78178E490329AEFB494FA415B9CEF25C19576D6B79A72BA2272013B5D03D40D321300793EA99F5

openssl x509 -in c1.pem -text -noout

image-20220417200812039

得到,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 

image-20220417200942648

將簽名復制到文件signature,刪除空格和冒號

cat signature | tr -d '[:space:]:'

image-20220417201313400

得到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

用來生成哈希的證書的主體:

image-20220417201524472

簽名塊:

image-20220417201600578

得到證書的主體,並計算它的哈希值

image-20220417201712580

哈希值為:

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

image-20220417202037237

得到,驗證結果為:

01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D0609608648016503040201050004207061DF0A50B8F2BA3367ECFABAB273A16F3BB1378DBE1FE524E6DFD90DFA3B91

根據sha256WithRSAEncryption可知,發放證書用到的hash算法是sha256,即得到的hash值共有256位,256/4=64。再根據於X.509證書簽名驗證的流程可知,只需要將上述結果的后64個十六進制數與step4得到的hash值做比較即可。

使用python實現:

m = "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D0609608648016503040201050004207061DF0A50B8F2BA3367ECFABAB273A16F3BB1378DBE1FE524E6DFD90DFA3B91"

hash = "7061df0a50b8f2ba3367ecfabab273a16f3bb1378dbe1fe524e6dfd90dfa3b91"
print(m[-64:].lower()==hash )

image-20220417202536730

驗證成功,簽名有效!

python
import gmpy2

S = 0xaa9fbe5d911bade44e4ecc8f07644435b4ad3b133fc129d8b4abf3425149463bd6cf1e4183e10b572f83697965076f59038c51948918103e1e5cedba3d8e4f1a1492d32bffd498cba7930ebcb71b93a4424246d9e5b11a6b682a9b2e48a92f1d2ab0e3f820945481502eeed7e0207a7b2e67fbfad817a45bdcca0062ef23af7a58f07a740cbd4d43f18c0287dce3ae09d2f7fa373cd24bab04e543a5d255110e41875f38a8e57a5e4c46b8b6fa3fc34bcd4035ffe0a471740ac1208be3544784d518bd519b405ddd423012d13aa5639aaf9008d61bd1710b067190ebaeadafba5fc7db6b1e78a2b4d10623a763f3b543fa568c50177b1c1b4e106b220e845294
n = 0xC14BB3654770BCDD4F58DBEC9CEDC366E51F311354AD4A66461F2C0AEC6407E52EDCDCB90A20EDDFE3C4D09E9AA97A1D8288E51156DB1E9F58C251E72C340D2ED292E156CBF1795FB3BB87CA25037B9A52416610604F571349F0E8376783DFE7D34B674C2251A6DF0E9910ED57517426E27DC7CA622E131B7F238825536FC13458008B84FFF8BEA75849227B96ADA2889B15BCA07CDFE951A8D5B0ED37E236B4824B62B5499AECC767D6E33EF5E3D6125E44F1BF71427D58840380B18101FAF9CA32BBB48E278727C52B74D4A8D697DEC364F9CACE53A256BC78178E490329AEFB494FA415B9CEF25C19576D6B79A72BA2272013B5D03D40D321300793EA99F5
e = 0x010001

m = gmpy2.powmod(S, e, n)
hash = "7061df0a50b8f2ba3367ecfabab273a16f3bb1378dbe1fe524e6dfd90dfa3b91"
print(hex(m)[-64:].lower() == hash)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM