密碼學(xué)基礎(chǔ)3:密鑰文件格式完全解析

這是密碼學(xué)筆記第三篇烤低。之前兩篇分析了 RSA 算法和橢圓曲線密碼學(xué)的基本原理,從中可以知道 RSA 算法的本質(zhì)是大整數(shù)質(zhì)數(shù)因子分解计露,橢圓曲線密碼學(xué)的本質(zhì)是曲線上的打點啤挎,樸素的原理后面處處閃耀著數(shù)學(xué)之光。從理論到實踐炭玫,這篇文章來解析下日常使用的密鑰文件格式奈嘿。

文章主要解決下面幾個問題:

  • 使用 opensslssh-keygen 生成的 RSA 和 ECC 的密鑰文件里面存了什么內(nèi)容,用的什么編碼規(guī)則吞加?
  • 有些 RSA 私鑰頭部是 -----BEGIN RSA PRIVATE KEY-----裙犹,而有些又是 -----BEGIN PRIVATE KEY-----,它們存儲的內(nèi)容有什么區(qū)別衔憨?
  • 使用 openssl 生成的公鑰跟 ssh-keygen 生成的密鑰對中的公鑰格式不一樣叶圃,它們有什么區(qū)別?
  • opensslssh-keygen 都支持輸入密碼對私鑰加密以增強安全性巫财,它們加密方式分別是什么盗似?有什么不同之處嗎哩陕?

1 ASN.1

ASN.1(Abstract Syntax Notation One) 是一種數(shù)據(jù)描述語言平项,跟 protobuf 和 Thrift 這些接口描述語言類似赫舒,它通過模塊(module) 來描述數(shù)據(jù)結(jié)構(gòu)。ASN.1 最早用于電信領(lǐng)域闽瓢,后來在計算機密碼學(xué)中也有廣泛應(yīng)用接癌。

ASN.1 定義了一些數(shù)據(jù)類型來描述數(shù)據(jù)結(jié)構(gòu),包括基礎(chǔ)類型(如整數(shù)類型 INTEGER扣讼,布爾類型 BOOLEAN缺猛,字符串類型 OCTET STRING,BIT STRING)和結(jié)構(gòu)化類型(如結(jié)構(gòu)體類型 SEQUENCE椭符,列表類型 SEQUENCE OF 等)荔燎,完整類型列表見 [ASN.1 Types]。類型通常都有個類型標(biāo)簽销钝,其中類型標(biāo)簽分為通用的有咨、應(yīng)用自定義的、上下文特定的蒸健、以及私有的 4 種類型座享。其中通用的類型標(biāo)簽如下:

Type Tag Number
INTEGER 0x02
BIT STRING 0x03
OCTET STRING 0x04
NULL 0x05
OBJECT IDENTIFIER 0x06
SEQUENCE and SEQUENCE OF 0x10
IA5String 0x16
UTCTime 0x17

下面用ASN.1定義了一個名為 FooProtocol 的模塊,其中包含一個結(jié)構(gòu)體類型字段 FooQuestion似忧。結(jié)構(gòu)體 FooQuestion 包括一個整形字段 id 和一個字符串類型字段 question(IA5String 是不包括控制字符的字符串類型)渣叛。

FooProtocol DEFINITIONS ::= BEGIN
    FooQuestion ::= SEQUENCE {
        id INTEGER,
        question       IA5String
    }
END

ASN.1 只是描述了數(shù)據(jù)結(jié)構(gòu),并沒有指定怎么編碼數(shù)據(jù)盯捌。因此淳衙,出現(xiàn)了多種編碼規(guī)則以方便數(shù)據(jù)在網(wǎng)絡(luò)上傳輸和不同終端間交互。比較常見的有 XER, JER, BER, DER等饺著。如待編碼的數(shù)據(jù)如下:

myQuestion FooQuestion ::= SEQUENCE {
    id 5,
    question "Anybody there?"
}

使用各編碼規(guī)則編碼結(jié)果如下滤祖,其中 XER 和 JER 不用多說,BER 和 DER 是最常見的密鑰文件編碼規(guī)則瓶籽,下一節(jié)詳細(xì)分析匠童。

                    XER(XML Encoding Rules)
            <FooQuestion>
                <id>5</id>
                <question>Anybody there?</question>
            </FooQuestion>

                    JER(JSON Encoding Rules)
    { "id" : 5, "question" : "Anybody there?" }
        
                    BER(Basic Encoding Rules)
        30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f

                    DER(Distinguished Encoding Rules)
        30 13 02 01 05 16 0e 41 6e 79 62 6f 64 79 20 74 68 65 72 65 3f

2 編碼規(guī)則

前文提到 ASN.1 只是定義了數(shù)據(jù)結(jié)構(gòu),并未規(guī)定具體的編碼方式塑顺,與之對應(yīng)的有多種編碼規(guī)則汤求。ASN.1 與特定的編碼規(guī)則一起通過使用獨立于計算機架構(gòu)和編程語言的方法來描述數(shù)據(jù)結(jié)構(gòu),為結(jié)構(gòu)化數(shù)據(jù)的表示严拒、編碼扬绪、傳輸、解碼提供了便利裤唠。本節(jié)主要介紹密鑰和證書中常見的編碼規(guī)則 BER挤牛,DER,以及由 DER 衍生出的密鑰文件格式 PEM种蘸。

BER (Basic Encoding Rules)

BER 是基礎(chǔ)編碼規(guī)則墓赴,編碼結(jié)構(gòu)包括類型標(biāo)志竞膳、長度,值以及結(jié)束符(可選)诫硕,每個字段以 8bit 即字節(jié)進(jìn)行分割坦辟。

----------------------------------------------------
| Identifier | Length | Contents | End-of-contents |
|   octets   | octets |  octets  |      octets     |
----------------------------------------------------

Identifier: 類型標(biāo)志,就是ASN.1 規(guī)定的類型章办,只是除了標(biāo)簽號(tag number)外锉走,還加了 3 位,第 7藕届,8 位用于區(qū)分是通用的標(biāo)簽類型還是其他標(biāo)簽類型挪蹭, 第 6 位 用于區(qū)分是基礎(chǔ)類型還是結(jié)構(gòu)化類型。Identifier 結(jié)構(gòu)如下休偶,后面我們會看到密鑰中的結(jié)構(gòu)體類型 SEQUENCE 的 Identifier 為 0x30嚣潜,即是由這個格式而來(0011000)。

---------------------------------
| 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---------------------------------
|Class  |P/C| Tag Number        |
---------------------------------
  • Class:如果是通用類型標(biāo)簽則為 00椅贱,應(yīng)用自定義的類型標(biāo)簽則為 01懂算,上下文特定類型標(biāo)簽 10,私有類型標(biāo)簽 11庇麦。
  • P/C:如果是基礎(chǔ)類型计技,則為 0,結(jié)構(gòu)化類型為 1山橄。
  • Tag Number:就是 ASN.1 中定義的數(shù)據(jù)類型標(biāo)簽號垮媒。

Length: 分三種情況,

  • 1)數(shù)據(jù)長度 < 128:則 Length 的 8bit首位為0航棱,其他7位表示數(shù)據(jù)長度睡雇。
  • 2)數(shù)據(jù)長度 >= 128:則 Length 的第一個8bit為 0x8?,其中 ? 是后面跟的是長度饮醇。比如 0x81 表示后面一個字節(jié)為長度它抱,如果是 0x82 則表示后面兩個字節(jié)為長度,以此類推朴艰。
  • 3)如果數(shù)據(jù)長度未知观蓄,則 Length=0x80,并增加 End-of-contents=00 00 結(jié)束標(biāo)記祠墅。

Contents & End-of-contents: 數(shù)據(jù)內(nèi)容侮穿。對于未知數(shù)據(jù)長度的數(shù)據(jù)類型被碗,才有 End-of-contents凳忙,為00 00宵喂。

實例:

  • 使用 OCTET STRING 編碼字符串 Hello空民,為 04 05 48 65 6C 6C 6F壶谒,即類型為 04黔姜,長度為 05甘有,內(nèi)容為 0x48 65 6C 6C 6F土居,即 Hello 的 ASCII 碼。
  • 使用 INTEGER 編碼整數(shù) 129娶耍,為 02 81 81
  • 結(jié)構(gòu)化類型是包含了多個數(shù)據(jù)類型的復(fù)合類型饼酿,后面詳細(xì)分析榕酒。

DER (Distinguished Encoding Rules)

DER 是典型的 Tag-Length-Value(TLV) 編碼方式,是 PKCS 密鑰體系常用的編碼故俐。
DER 是 BER 的子集想鹰,編碼規(guī)則幾乎一樣,不過去掉了 BER 的一些靈活性药版,多了幾個限制:

  • 如果數(shù)據(jù)長度在 0-127 之間辑舷,則 Length 必須使用第 1 種編碼方式。
  • 如果數(shù)據(jù)長度 >= 128槽片,則 Length 必須使用第 2 種編碼方式何缓,且 Length 必須用最少的字節(jié)編碼,如果能用 2 字節(jié)的則不能用 3 字節(jié)还栓。
  • 數(shù)據(jù)要用明確長度的編碼方式碌廓,不支持 Length 的第3種編碼即未知數(shù)據(jù)長度+結(jié)束標(biāo)記的方式。

注意:ASN.1 規(guī)定整型 INTEGER 需要支持正整數(shù)剩盒、負(fù)整數(shù)和零谷婆。BER/DER 使用大端模式存儲 INTEGER,并通過最高位來編碼正負(fù)數(shù)(最高位0為正數(shù)辽聊,1為負(fù)數(shù))纪挎。 如果密鑰參數(shù)值最高位為 1,則 BER/DER 編碼會在參數(shù)前額外加 0x00 以表示正數(shù)跟匆,這也就是為什么有時候密鑰參數(shù)前面會多出1字節(jié) 0x00 的原因异袄。

PEM(Privacy Enhanced Mail): 因為 DER 編碼是二進(jìn)制數(shù)據(jù),早期的 Email 不能發(fā)送附件玛臂,也不方便直接傳輸二進(jìn)制數(shù)據(jù)([原因])隙轻,因此密鑰文件通常會在 DER 編碼基礎(chǔ)上進(jìn)行 Base64 編碼,這就是經(jīng)彻缚看到的密鑰文件格式 PEM玖绿。PEM 最早是用來增強郵件安全性的,不過沒有被廣泛接受叁巨,最后卻是在密碼學(xué)中得到了發(fā)揚光大斑匪,如 openssl 和 ssh-keygen 工具生成的公私鑰文件默認(rèn)都采用 PEM 格式。需要注意的是,PEM 本身不是 ASN.1 的編碼規(guī)則蚀瘸,它只是 Base64-encoded DER狡蝶。

3 密鑰格式解析

ASN.1 編碼規(guī)則只定義了數(shù)據(jù)編碼方式,但是并沒有賦予數(shù)據(jù)意義贮勃。公鑰密碼學(xué)標(biāo)準(zhǔn) PKCS (Public Key Cryptography Standards) 和公鑰基礎(chǔ)設(shè)施 PKIX(Public-Key Infrastructure X.509) 等使用 ASN.1 的類型定義密鑰和證書的格式和編碼贪惹,以描述公私鑰和證書屬性。 需要注意寂嘉,PKCS 雖然名字是公鑰密碼學(xué)標(biāo)準(zhǔn)奏瞬,它其實也包括私鑰格式標(biāo)準(zhǔn)。 這兩個標(biāo)準(zhǔn)的內(nèi)容浩瀚如煙泉孩,本節(jié)只分析常見幾種密鑰相關(guān)的部分硼端。

3.1 PKCS #1

PKCS #1 是 RSA Cryptography Specifications,即 RSA 密碼學(xué)規(guī)范寓搬,它在 [rfc8017] 中有詳細(xì)說明珍昨,定義了 RSA 密鑰文件的格式和編碼方式,以及加解密句喷、簽名镣典、填充的基礎(chǔ)算法。

RSA 密鑰格式

RSA 公私鑰的 ASN.1 類型定義如下唾琼,根據(jù)類型定義和數(shù)據(jù)編碼骆撇,就能解析出 RSA 公私鑰中的參數(shù)了(參數(shù)含義請參考我之前的《RSA算法原理解析》一文)。


 RSAPublicKey ::= SEQUENCE {
        modulus           INTEGER,  -- n
        publicExponent    INTEGER   -- e
 }
         
 RSAPrivateKey ::= SEQUENCE {
        version           Version,
        modulus           INTEGER,  -- n
        publicExponent    INTEGER,  -- e
        privateExponent   INTEGER,  -- d
        prime1            INTEGER,  -- p
        prime2            INTEGER,  -- q
        exponent1         INTEGER,  -- d mod (p-1)
        exponent2         INTEGER,  -- d mod (q-1)
        coefficient       INTEGER,  -- (inverse of q) mod p
        otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

使用 openssl 生成的一對 RSA 公私鑰 (示例為方便展示父叙,用的 1024 位密鑰神郊,實際中請使用 2048 位以上)

$ openssl genrsa -out prikey.p1 1024
$ openssl rsa -in prikey.p1 -pubout -RSAPublicKey_out > pubkey.p1

PKCS#1 格式解析如下:公鑰的 SEQUENCE 包括 RSA 公鑰參數(shù) n 和 e 兩個屬性。RSA 私鑰則首先是版本號 0趾唱,然后是 RSA 私鑰的 8 個參數(shù)涌乳。

PKCS#1 公鑰
PKCS#1 私鑰

加密私鑰

可以對 PKCS#1 的私鑰進(jìn)行加密,如 ssh-keygen 可以指定 passphrase (測試的密碼是 testtest)加密 RSA 私鑰甜癞,加密后的私鑰 enc_prikey.p1 格式如下:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,8C2A8D6593F411D7336B842037B5200B

EncryptedRSAPrivateKey
-----END RSA PRIVATE KEY-----

DEK-Info 里面指明了加密算法是 AES-128-CBC夕晓,IV 是 8C2A8D6593F411D7336B842037B5200B,AES加密的實際密碼=md5(設(shè)定密碼 + IV的前8個字節(jié))悠咱≌袅荆可以使用 openssl aes-128-cbc 驗證加密結(jié)果是否與 EncryptedRSAPrivateKey 一致。

$ tail -n +2 prikey.p1 | grep -v 'END RSA' | base64 -d | 
openssl aes-128-cbc -e -iv 8C2A8D6593F411D7336B842037B5200B -K $(python -c "exec(\"import hashlib\\nprint hashlib.md5(bytearray('testtest') + bytearray.fromhex('8C2A8D6593F411D7')).hexdigest()\")") | base64

3.2 PKCS #8

PKCS#8 是 Private-Key Information Syntax Standard析既,即私鑰格式相關(guān)的標(biāo)準(zhǔn)躬贡,它不像 PKCS#1 只支持 RSA,而是支持各種類型的私鑰眼坏。PKCS#8 私鑰文件格式中首尾并未說明私鑰算法類型拂玻,算法類型在數(shù)據(jù)中標(biāo)識。PKCS#8 中的私鑰也支持加密。

未加密私鑰格式

未加密私鑰格式的 ASN.1 類型定義如下(參見 [rfc5958] ):

    OneAsymmetricKey ::= SEQUENCE {
        version                   Version,
        privateKeyAlgorithm       PrivateKeyAlgorithmIdentifier,
        privateKey                PrivateKey,
        attributes            [0] Attributes OPTIONAL,
       ...,
       [[2: publicKey        [1] PublicKey OPTIONAL ]],
       ...}

        Version ::= INTEGER  # 版本號檐蚜。
    
        PrivateKeyAlgorithmIdentifier ::= SEQUENCE  { # 密鑰算法標(biāo)識
                algorithm               OBJECT IDENTIFIER,
                parameters              ANY DEFINED BY algorithm OPTIONAL  }
    
        PrivateKey ::= OCTET STRING # 不同類型的私鑰格式不同魄懂,比如 RSA 的是 RSAPrivateKey類型,而 ECC 的是 ECPrivateKey 類型闯第。
            
        Attributes ::= SET OF Attribute # 跟公鑰相關(guān)的屬性市栗,比如證書什么的,在公私鑰中通常為空咳短。
        
        PublicKey ::= BIT STRING # 不同類型密鑰包含的公鑰內(nèi)容也不同填帽。

RSA 私鑰格式

可以使用 openssl 將 PKCS#1 格式的私鑰 prikey.p1 轉(zhuǎn)換成 PKCS#8 格式的 prikey.p8,如下:

$ openssl pkcs8 -in prikey.p1 -topk8 -out prikey.p8 -nocrypt

私鑰格式解析如下:

PKCS#8 RSA私鑰格式
  • version:版本號诲泌,目前值為 0盲赊。
  • privateKeyAlgorithm:私鑰算法铣鹏, rsaEncryptionOBJECT IDENTIFIER1.2.840.113549.1.1.1敷扫,具體含義參見 [這里]
  • privateKey:私鑰诚卸,OCTET STRING 類型葵第,里面其實封裝了一個 RSAPrivateKey 類型,跟 PKCS#1 一樣合溺。
  • attributes 和 publicKey 為空卒密。

ECC 私鑰格式

橢圓曲線類型的私鑰格式在 rfc5915 中定義如下:

ECPrivateKey ::= SEQUENCE {
     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
     privateKey     OCTET STRING,
     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
     publicKey  [1] BIT STRING OPTIONAL
}

使用 openssl 創(chuàng)建一個 PKCS#8 格式的 ecc 密鑰,采用 prime256v1 曲線:

# 生成傳統(tǒng)格式的 ECC 私鑰棠赛,類似 PKCS#1 那樣哮奇,只包含 privateKey,密鑰類型在頭部 -----BEGIN EC PRIVATE KEY----- 標(biāo)識睛约,橢圓曲線在 parameters 標(biāo)識鼎俘。
$ openssl ecparam -name prime256v1 -genkey -noout -out ecc_prikey.tradfile

# 轉(zhuǎn)換為 PKCS#8 格式
$ openssl pkcs8 -topk8 -in ecc_prikey.tradfile -out ecc_prikey.p8 -nocrypt
$ cat ecc_prikey.p8
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMbahscIGpSZ6NULI
iQ/pTI9ZcvFdXKtjN1bAGO2bxvahRANCAATwq1k9rx/8neP8MqVR7UuJ98bLFsU5
jpueH0ougZNWrsKUki0cgKDGrb3C8Q2NMRO336ve22Xk674lk/ZDHkAV
-----END PRIVATE KEY-----

ASN.1 格式解析如下:

PKCS#8 ECC私鑰格式
  • 前面部分是算法標(biāo)識 1.2.840.10045.2.1(ecPublicKey) 和 1.2.840.10045.3.1.7 (prime256v1)
  • 后面是私鑰信息辩涝,其中包括了版本號 1贸伐,OCTET STRING 類型私鑰 31B6A1...F6,BIT STRING 類型的公鑰 0000 0100 1111 0000...怔揩。

當(dāng)然通過 openssl 可以直接解析出公私鑰和曲線類型捉邢,如下:

$ openssl ec -in ecc_prikey.p8 -noout -text
read EC key
Private-Key: (256 bit)
priv:
    31:b6:a1:b1:c2:06:a5:26:7a:35:42:c8:89:0f:e9:
    4c:8f:59:72:f1:5d:5c:ab:63:37:56:c0:18:ed:9b:
    c6:f6
pub:
    04:f0:ab:59:3d:af:1f:fc:9d:e3:fc:32:a5:51:ed:
    4b:89:f7:c6:cb:16:c5:39:8e:9b:9e:1f:4a:2e:81:
    93:56:ae:c2:94:92:2d:1c:80:a0:c6:ad:bd:c2:f1:
    0d:8d:31:13:b7:df:ab:de:db:65:e4:eb:be:25:93:
    f6:43:1e:40:15
ASN1 OID: prime256v1
NIST CURVE: P-256

在上一篇《橢圓曲線密碼學(xué)原理分析》一文知道,橢圓曲線的密鑰生成其實就是一個公式 P = nG商膊,n 就是私鑰伏伐,G 是基點,P 是公鑰晕拆。注意到這里公鑰的第一個字節(jié) 04 表示公鑰格式是 uncompressed format秘案,即非壓縮格式,也就是把點的 X 和 Y 坐標(biāo)合到一起作為公鑰。壓縮格式就是只用 X 坐標(biāo)或者 Y 坐標(biāo)中的一個阱高,另一個坐標(biāo)根據(jù)曲線方程可以求得([rfc5480] 有詳細(xì)說明)赚导。可以通過 libnum 庫來驗證下公私鑰的準(zhǔn)確性赤惊。

$ cat ecc.py 
from libnum.ecc import Curve
curve = Curve(
        a=0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
        b=0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
        p=0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
        g = (0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
            0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
)

pri = 0x31b6a1b1c206a5267a3542c8890fe94c8f5972f15d5cab633756c018ed9bc6f6
pub = curve.power(curve.g, pri)
print hex(pub[0]), hex(pub[1])
$ python ecc.py 
('0xf0ab593daf1ffc9de3fc32a551ed4b89f7c6cb16c5398e9b9e1f4a2e819356aeL', 
'0xc294922d1c80a0c6adbdc2f10d8d3113b7dfabdedb65e4ebbe2593f6431e4015L')

加密私鑰格式

PKCS#8 里面對私鑰加密提供了 PBES2(Password-Based Encryption Scheme 2)加密模式支持吼旧。通過 PBKDF2(Password-Based Key Derivation Function 2) 對原始密碼進(jìn)行多次哈希處理作為加密密碼以增強破解難度,然后用對稱加密算法 AES 或者 DES 對私鑰進(jìn)行加密未舟。

PBKDF2 是一種 CPU 密集型算法圈暗,但是如果使用 GPU 陣列或者 FPGA 來破解還是相對容易。在密碼存儲中現(xiàn)在更傾向于用 Bcrypt裕膀,它不僅是 CPU 運算密集员串,而且是內(nèi)存密集,破解難度會更高一些昼扛。不過總的來說寸齐, PBKDF2 比 ssh-keygen 的 md5 方式生存密碼安全性會高很多。

PKCS#8 加密類型私鑰的 ASN.1 類型定義如下:

EncryptedPrivateKeyInfo ::= SEQUENCE {
     encryptionAlgorithm  EncryptionAlgorithmIdentifier,
     encryptedData        EncryptedData }

     EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
                                        { CONTENT-ENCRYPTION,
                                          { KeyEncryptionAlgorithms } }

     EncryptedData ::= OCTET STRING

使用 openssl 對 PKCS#8 格式的密鑰加密是很方便的抄谐,默認(rèn)就支持(生成密鑰時不加 -nocrypt 參數(shù)即可)渺鹦。加密后的 ecc_prikey.p8 格式解析如下:

加密的 PKCS#8 ECC私鑰格式
  • 其中加密模式是 pkcs5PBES2,密鑰生成算法是 PBKDF2蛹含,參數(shù) salt= E20EED9A112B7BFA毅厚,iteration=2048,哈希算法是 hmacWithSHA256浦箱。
  • 對稱加密算法是 aes256-cbc吸耿,參數(shù) iv=27579581D081AEDA083889370232AD1A
  • 最后一行 11E9C5C2.... 就是加密私鑰 encryptedData酷窥。

加密過程解析:

  • 先使用密鑰生成算法 PBKDF2 生成加密密碼咽安,python 可以用 backports.pbkdf2 模塊。
import os, binascii
from backports.pbkdf2 import pbkdf2_hmac

salt = binascii.unhexlify('E20EED9A112B7BFA')
passwd = b"testtest"
key = pbkdf2_hmac("sha256", passwd, salt, 2048, 32)
print("Derived key:", binascii.hexlify(key))

# 輸出: ('Derived key:', 'bf48084fd98fcbacd8e024166efb7232c897282fe7e4ff836db3f3d81e32ede9')
  • 然后使用 openssl 的 aes256-cbc 加密原私鑰竖幔,可以驗證跟 encryptedData 是一樣的板乙。
$ tail -n +2 ecc_prikey.p8 | grep -v 'END '| base64 -d | 
openssl aes-256-cbc -e -iv 27579581D081AEDA083889370232AD1A -K bf48084fd98fcbacd8e024166efb7232c897282fe7e4ff836db3f3d81e32ede9 | hexdump -C

00000000  11 e9 c5 c2 4c c3 2d bb  fa 84 b9 fb db f1 d1 ff  |....L.-.........|
00000010  f0 6a 5b fa c3 a6 88 cd  02 4c ac 52 84 f4 cb c1  |.j[......L.R....|
......
00000080  8f 72 96 7a 58 aa 1f 5a  6f c1 bf dc 43 1a 46 26  |.r.zX..Zo...C.F&|

3.3 PKIX

前面提到 PKCS#8 定義了通用的私鑰格式支持各類私鑰,在 PKIX ([rfc5280]) 中也定義了通用的公鑰格式拳氢。其中包括算法標(biāo)識和公鑰內(nèi)容募逞,算法標(biāo)識 AlgorithmIdentifier 與前面私鑰中的 PrivateAlgorithmIdentifier 是類似的。

SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm         AlgorithmIdentifier,
        subjectPublicKey  BIT STRING }

        AlgorithmIdentifier  ::=  SEQUENCE  {
                algorithm               OBJECT IDENTIFIER,
                parameters              ANY DEFINED BY algorithm OPTIONAL  
        }

將之前的 PKCS#1 格式的 RSA 公鑰轉(zhuǎn)換成 PKIX 的格式:

$ openssl rsa -RSAPublicKey_in -in ../pk1/pubkey.p1 -pubout > pubkey.pkix

PKIX 格式的公鑰解析如下馋评,包括公鑰算法 rsaEncryption 和 RSA 公鑰參數(shù) n 和 e放接。

PKIX公鑰格式

3.4 openssl 和 ssh-keygen 生成公鑰格式區(qū)別

相信大家會發(fā)現(xiàn) ssh-keygen -t rsa 生成的 RSA 密鑰對中公鑰格式跟 PKCS#1 和 PKIX 中的都不一樣。ssh-keygen 生成的公鑰 id_rsa.pub 如下所示:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+gCiA//vUMu/2dYj9oGUpY2TCw5/AtkfI2cWvl7hOkliQd7uI61gE9BV5w+Ib+HnjAB9lFYS4A8rlpRlkH9a+mCN2K/Oh5dhoonxat4qeHB5XDvmImUfdOGayT5l176KWP4ftGJt+8ygRpo05zcbuBrd/KxFZ7KDiQyXRvRv9mw== vagrant@stretch

這是 openssh 使用的一種專屬格式:

[type-name] [base64-encoded-ssh-public-key] [comment]

其中 base64-encoded-ssh-public-key 并沒有使用 ASN.1 的數(shù)據(jù)類型定義和 DER 編碼留特,而是使用的 SSH 協(xié)議定義的格式(見 [rfc4251])纠脾,分為 3 部分:

string    "ssh-rsa"
mpint     e
mpint     n

這里的 string 類型和 mpint 類型在 rfc4251 中定義玛瘸,其中 mpint是使用字符串的方式來存儲整數(shù),它們都會在數(shù)據(jù)前先用4字節(jié)存儲數(shù)據(jù)長度苟蹈。id_rsa.pub 解析后格式如下所示:

00 00 00 07  73 73 68 2d 72 73 61  | 長度 7糊渊,值 ssh-rsa|
00 00 00 03  01 00 01  | e 長度 3,值 0x010001 |
00 00 00 81  00 be 80 28 80 ff fb d4 |n 長度 127慧脱,值 0x00 be 80 28 80...|
......

也可以將 ssh-keygen 生成的公鑰轉(zhuǎn)換成 PKCS#1 的格式:

$ ssh-keygen -f id_rsa.pub -e -m pem > pubkey.p1

4 總結(jié)

PKCS 和 PKIX 使用 ASN.1 定義了密鑰的數(shù)據(jù)結(jié)構(gòu)渺绒,并使用 DER 編碼規(guī)則編碼密鑰,最終使用 PEM (Base64-encoded DER) 格式將密鑰數(shù)據(jù)存儲在密鑰文件中菱鸥。

PKCS#1 首部會標(biāo)識密鑰算法類型宗兼,PKCS#8 則是在密鑰數(shù)據(jù)中有字段專門存儲密鑰算法。openssh 使用的公鑰格式是 SSH 協(xié)議中定義的氮采,雖然參數(shù)值一樣殷绍,但是編碼方式與 PKCS 和 PKIX 標(biāo)準(zhǔn)都不同。

ssh-keygen 是對原始密碼和初始向量經(jīng)過一個簡單規(guī)則 md5 后生成加密密碼鹊漠,然后使用 aes128-cbc 對稱加密主到。而 openssl 則是對原始密碼采用 PBKDF2 算法生成加密密碼,然后使用 aes256-cbc 對稱加密贸呢,openssl 的加密方式更安全一些镰烧。

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末楞陷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子茉唉,更是在濱河造成了極大的恐慌固蛾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件度陆,死亡現(xiàn)場離奇詭異艾凯,居然都是意外死亡,警方通過查閱死者的電腦和手機懂傀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門趾诗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹬蚁,你說我怎么就攤上這事恃泪。” “怎么了犀斋?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵贝乎,是天一觀的道長。 經(jīng)常有香客問我叽粹,道長览效,這世上最難降的妖魔是什么却舀? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锤灿,結(jié)果婚禮上挽拔,老公的妹妹穿的比我還像新娘。我一直安慰自己但校,他們只是感情好篱昔,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著始腾,像睡著了一般州刽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上浪箭,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天穗椅,我揣著相機與錄音,去河邊找鬼奶栖。 笑死匹表,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宣鄙。 我是一名探鬼主播袍镀,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冻晤!你這毒婦竟也來了苇羡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤鼻弧,失蹤者是張志新(化名)和其女友劉穎设江,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攘轩,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡叉存,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了度帮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歼捏。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笨篷,靈堂內(nèi)的尸體忽然破棺而出瞳秽,到底是詐尸還是另有隱情,我是刑警寧澤冕屯,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布寂诱,位于F島的核電站,受9級特大地震影響安聘,放射性物質(zhì)發(fā)生泄漏痰洒。R本人自食惡果不足惜瓢棒,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丘喻。 院中可真熱鬧脯宿,春花似錦、人聲如沸泉粉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗡靡。三九已至跺撼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讨彼,已是汗流浹背歉井。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哈误,地道東北人哩至。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像蜜自,于是被迫代替她去往敵國和親菩貌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容