非對稱演算法之RSA的簽名剖析
*本文作者:liang亮,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
前言
數字簽名,就是隻有信息的傳送者才能產生的別人無法偽造的一段數字串,這段數字串同時也是對資訊的傳送者傳送資訊真實性的一個有效證明。 不清楚的請自行科普 ofollow" rel="nofollow,noindex" target="_blank">數字簽名 。本篇主要來講簽名值具體是怎麼計算出來的~
一、動手解密簽名值
1、測試金鑰
//隨機產生RSA私鑰。因1024位的RSA金鑰已不安全,本次測試使用2048長度的RSA金鑰。 > openssl genrsa2048 Generating RSA private key, 2048 bit long modulus .....................+++ ............................................................................................................................+++ e is 65537 (0x10001) -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqmk7bbqdfdDWgQZ/srfpBurN40Rw8QqBjoE8cujbF8zHrNJc RlhHVRQ9HRwHAkG0xM5OzZjfzxHseJ+D3v84xEwHrd5l0t/iMVZxIOoyHk0gHDKv kFAaSk+Rlo61bqxgjcRTNkz9mpA8hmG55fYsl1ktJ05YA4rHSv33Dp5OsPGIlO+H RZRjdsu8iYfFUrPmfjoVZKPhiRHEeYtDxfyu3rUOlfdkLQyFmlrVJAMdatpM2paz eSY3ypqR0mClQ3+t4GpGrCjXqkFYOXP/02YpDKrO04zHtOxZNnZz7r3YedCgzG4Z uBvZGin3ig/X2fxZLJmmERhv9FizGvM541kA7wIDAQABAoIBACS/FGWQ/C0JP3gL IrYzGji3oTYEqCYSEeXc0GAm/jefnN8TbXptxtP9zT/dr1U5PfXCVxPWh0xrnQZV v2Xyuxb5Hh7L+kECrg/dh/+FANGv5+CsvVbtLv4fMlG47D61RQzM7PSknXsa5zJD GIcSEoOAY1A6gJgi8N6m7QNl96oH0TiTbs4xIo8wTgwPR2fkXFB/XySqweOZBeNW W6cGFb382lM2N51G72ElQe27+O214J/bHKDukoGgJ5GE2uI2r0xkIXYLsy+zeOdG gi1UEqjLng596wvH55hog+4b2aPQP2yxnzvtzQELb6XY/oApgQQd5HH5JpWa8ec3 YJBmp0ECgYEA2Y9YyGp6zsRRTKFuqt1ISQltlFg6pxSAGJYbUPcLD2x67hXdfnhB s1fvNvG+hYh65e80/HZca8JKTB4ETU0oOPQfzoGRqa5pIxv37QIruskyu2Skevlo CMAT7EXO1CT5ewce1We1R3vUVIlBI3/JibsI89VyBlyDK2QnUKltBtkCgYEAyIVA PWnYt0EsA22LdFLcgOQ/W/LMYRssaxpbde9yXqp7MKhhAGFq33IWWYXaGhBVbslC Co5cLPrcPiNvB+lFPfFfmBTncU9FGnkDCiAGCufWuqHGSc2mmVnn/pdUdwqI+jSW tqCYQl5YO5R39sbjTPyilWWAuS0lUS9rLm3juQcCgYEAr6p8II9Bi/SeRIbQqew0 sqyHK1G2QjReXfvOIKjo6FJKTDWhe720JxBome/GS2HxAfoMyZD0fRoLDbzq8lPL l3keuYqLR/wI7o1luZyYHKDacs8HtDfv1ajqLUwMfeVBACK2tc+gYxDMWFnfG7/R xoEb8G43PIW0b/PVft7eprkCgYAZK/kTfI0S/CBtUbwW3ywFFiIKBeG4MvQRgd6H YIan8ZjDU+/RX2lOIYFCvbXSXciLvsIGlzZlAxzQxBv1D0h87ScF7WHcbIoNN7G0 /K4lglMHXLWKoEFQsOOZpx+YTf9CAYYF6QUUF8nVuN6SYQc5q+ExBevx0wQDPAOl cXAL0wKBgENcM4Gc0yAn8dRP803/I90s9zgHapKTjDCw6o3q331VNq90orWQj4hp bj2BovL6jH2OatpMEv3lLxHXT+pfG9VMGXV79h6AGo5VrLBAMPzAua1xr3nZTWiB EQd+4FF41ku94XsCbOwEdNgxtIw33m7OZmgYzajSPILvvI35DNnH -----END RSA PRIVATE KEY----- // 通過生成的私鑰,得到公鑰 > opensslrsa -pubout -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAqmk7bbqdfdDWgQZ/srfpBurN40Rw8QqBjoE8cujbF8zHrNJc RlhHVRQ9HRwHAkG0xM5OzZjfzxHseJ+D3v84xEwHrd5l0t/iMVZxIOoyHk0gHDKv kFAaSk+Rlo61bqxgjcRTNkz9mpA8hmG55fYsl1ktJ05YA4rHSv33Dp5OsPGIlO+H RZRjdsu8iYfFUrPmfjoVZKPhiRHEeYtDxfyu3rUOlfdkLQyFmlrVJAMdatpM2paz eSY3ypqR0mClQ3+t4GpGrCjXqkFYOXP/02YpDKrO04zHtOxZNnZz7r3YedCgzG4Z uBvZGin3ig/X2fxZLJmmERhv9FizGvM541kA7wIDAQABAoIBACS/FGWQ/C0JP3gL IrYzGji3oTYEqCYSEeXc0GAm/jefnN8TbXptxtP9zT/dr1U5PfXCVxPWh0xrnQZV v2Xyuxb5Hh7L+kECrg/dh/+FANGv5+CsvVbtLv4fMlG47D61RQzM7PSknXsa5zJD GIcSEoOAY1A6gJgi8N6m7QNl96oH0TiTbs4xIo8wTgwPR2fkXFB/XySqweOZBeNW W6cGFb382lM2N51G72ElQe27+O214J/bHKDukoGgJ5GE2uI2r0xkIXYLsy+zeOdG gi1UEqjLng596wvH55hog+4b2aPQP2yxnzvtzQELb6XY/oApgQQd5HH5JpWa8ec3 YJBmp0ECgYEA2Y9YyGp6zsRRTKFuqt1ISQltlFg6pxSAGJYbUPcLD2x67hXdfnhB s1fvNvG+hYh65e80/HZca8JKTB4ETU0oOPQfzoGRqa5pIxv37QIruskyu2Skevlo CMAT7EXO1CT5ewce1We1R3vUVIlBI3/JibsI89VyBlyDK2QnUKltBtkCgYEAyIVA PWnYt0EsA22LdFLcgOQ/W/LMYRssaxpbde9yXqp7MKhhAGFq33IWWYXaGhBVbslC Co5cLPrcPiNvB+lFPfFfmBTncU9FGnkDCiAGCufWuqHGSc2mmVnn/pdUdwqI+jSW tqCYQl5YO5R39sbjTPyilWWAuS0lUS9rLm3juQcCgYEAr6p8II9Bi/SeRIbQqew0 sqyHK1G2QjReXfvOIKjo6FJKTDWhe720JxBome/GS2HxAfoMyZD0fRoLDbzq8lPL l3keuYqLR/wI7o1luZyYHKDacs8HtDfv1ajqLUwMfeVBACK2tc+gYxDMWFnfG7/R xoEb8G43PIW0b/PVft7eprkCgYAZK/kTfI0S/CBtUbwW3ywFFiIKBeG4MvQRgd6H YIan8ZjDU+/RX2lOIYFCvbXSXciLvsIGlzZlAxzQxBv1D0h87ScF7WHcbIoNN7G0 /K4lglMHXLWKoEFQsOOZpx+YTf9CAYYF6QUUF8nVuN6SYQc5q+ExBevx0wQDPAOl cXAL0wKBgENcM4Gc0yAn8dRP803/I90s9zgHapKTjDCw6o3q331VNq90orWQj4hp bj2BovL6jH2OatpMEv3lLxHXT+pfG9VMGXV79h6AGo5VrLBAMPzAua1xr3nZTWiB EQd+4FF41ku94XsCbOwEdNgxtIw33m7OZmgYzajSPILvvI35DNnH -----END RSA PRIVATE KEY----- writing RSA key -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqmk7bbqdfdDWgQZ/srfp BurN40Rw8QqBjoE8cujbF8zHrNJcRlhHVRQ9HRwHAkG0xM5OzZjfzxHseJ+D3v84 xEwHrd5l0t/iMVZxIOoyHk0gHDKvkFAaSk+Rlo61bqxgjcRTNkz9mpA8hmG55fYs l1ktJ05YA4rHSv33Dp5OsPGIlO+HRZRjdsu8iYfFUrPmfjoVZKPhiRHEeYtDxfyu 3rUOlfdkLQyFmlrVJAMdatpM2pazeSY3ypqR0mClQ3+t4GpGrCjXqkFYOXP/02Yp DKrO04zHtOxZNnZz7r3YedCgzG4ZuBvZGin3ig/X2fxZLJmmERhv9FizGvM541kA 7wIDAQAB -----END PUBLIC KEY-----
2、編寫程式碼解密
眾所周知:
RSA加密解密:私鑰解密,公鑰加密。
RSA數字簽名-俗稱加簽驗籤:私鑰加簽,公鑰驗籤。
其實:
也是有私鑰加密,公鑰解密的。只是因為公鑰是公開的,私鑰加密後所有人都可以解密,沒有意義,所以常用簽名,而不是加密。
私鑰加簽的本質也是私鑰加密資料的Hash值。
這裡有個小技巧:我們用公鑰對簽名值解密,使用RSA_NO_PADDING,這樣就能得到 簽名時私鑰加密的資料。
鑑於篇幅長度,程式碼只貼出關鍵部分。
程式碼之PHP:
/** * 建立簽名 * @param string $data 資料 * @return null|string */ public function createSign($data = '') { if (!is_string($data)) { return null; } openssl_sign($data, $sign, self::getPrivateKey(),OPENSSL_ALGO_SHA256); return$sign; } /** * 公鑰解密資料 * @param string $data 資料 * @return null|string */ public function decData($data = '') { if (!is_string($data)) { return null; } openssl_public_decrypt($data, $encData, self::getPublicKey(),OPENSSL_NO_PADDING); return$encData; } /** * 公鑰解密資料 * @param string $data 資料 * @return null|string */ public function decDataPKCS1Padding($data = '') { if (!is_string($data)) { return null; } openssl_public_decrypt($data, $encData, self::getPublicKey(),OPENSSL_PKCS1_PADDING); return$encData; }
其他語言程式碼整理ing…
本次測試 java、js、C#、PHP。結果均一致,如下:
使用示例私鑰進行簽名,得到如下結果 簽名值Base64: UjV/9zDoYcXTKKKDlWhFshQ08ikknKWqhCig8J7VhVrFCF+tFUQ85xnCUA0KR/t9CVFcf7SDA6ntr/T2xJ4T9TiAHEmNIhghZTlOsp+ieyvB5N4jQ6fuK6DjdtK/icklK5fmMozbKKiHwjr33lWY8NTfXydtKgd/5fPIVhUB26TyOKiO7JY3iAlylqfIpEy4g3fsxiiPGe022jIt400re1UCyZWbhXqdr2JUE7hAZEcMcbxvZff4Ilh6hHZ9rAkWBU1E2vsQUnnvdDK3BOQpZQvTtfIHbX+lhT5UcsFJrwOuONo0nG0s7MSjCsjQEf7iucEZpZeUYwOeMexVHsU9rw== 簽名值Hex: 52357ff730e861c5d328a283956845b21434f229249ca5aa8428a0f09ed5855ac5085fad15443ce719c2500d0a47fb7d09515c7fb48303a9edaff4f6c49e13f538801c498d22182165394eb29fa27b2bc1e4de2343a7ee2ba0e376d2bf89c9252b97e6328cdb28a887c23af7de5598f0d4df5f276d2a077fe5f3c8561501dba4f238a88eec963788097296a7c8a44cb88377ecc6288f19ed36da322de34d2b7b5502c9959b857a9daf625413b84064470c71bc6f65f7f822587a84767dac0916054d44dafb105279ef7432b704e429650bd3b5f2076d7fa5853e5472c149af03ae38da349c6d2cecc4a30ac8d011fee2b9c119a5979463039e31ec551ec53daf 使用示例公鑰進行NOPADDING解密,得到如下結果 原資料Base64: AAH/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ADAxMA0GCWCGSAFlAwQCAQUABCC5TSe5k00+CKUuUtfafav6xITv43pTgO6QiPes4u/N6Q== 原資料Hex: 0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d060960864801650304020105000420b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 使用示例公鑰進行PKCS1_PADDING解密,得到如下結果 原資料Base64: MDEwDQYJYIZIAWUDBAIBBQAEILlNJ7mTTT4IpS5S19p9q/rEhO/jelOA7pCI96zi783p 原資料Hex: 3031300d060960864801650304020105000420b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
簡單分析
1. 字串”hello world”進行sha256運算得到hash: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
2. 在Hash結果前資料填充:3031300d060960864801650304020105000420
3. PKCS1 在上一步結果前填充:
0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
二、結合資料分析RSA的補位
1、簽名時,對Hash值的資料填充方式
對hash演算法id和hash值進行ASN.1的DER編碼。如下:
DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }
為方便理解,我們使用ASN1dump對示例中的資料做解析:
3031300d060960864801650304020105000420b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
直接上圖:
可以看到sha256的演算法id,2.16.840.1.101.3.4.2.1:
資料也在其中,另 附上部分演算法id:
另因為各個hash演算法id是固定的,計算的結果也是固定的。所以不同的hash演算法的填充也是固定的。如下:
md2:"3020300c06082a864886f70d020205000410", md5:"3020300c06082a864886f70d020505000410", sha1:"3021300906052b0e03021a05000414", sha224:"302d300d06096086480165030402040500041c", sha256:"3031300d060960864801650304020105000420", sha384:"3041300d060960864801650304020205000430", sha512:"3051300d060960864801650304020305000440", ripemd160: "3021300906052b2403020105000414"
2、pkcs1padding V1.5的填充模式 參考 rfc2312
以下描述均以十六進位制字串來描述。
pkcs1padding V1.5的填充模式方式如下:
EB = 00+BT+PS +00 + D
即:加密塊=00+塊型別+填充字元+00+資料
1. 開頭00是為了確保塊轉換為整數的時候 小於模數
2. BT(Block Type):當使用私鑰操作,塊型別為00或01,公鑰操作,塊型別為02。塊型別為00,資料開頭必須不能是00,因為填充的也是00,將無法解析。塊型別為01或02,塊可以被準確解析,因為不會是00來填充。
3. PS(Padding String):k-3-||D|| 個位元組組組成,k表示金鑰的位元組長度, D表示明文資料D的位元組長度 。當BT為01時,填充位元組值為FF,BT為00時,填充位元組值為00,BT為02時填充隨機數(非00)。填充長度至少為8個位元組
4. 00,用於分開PS和D
5. D,資料原文(HEX)
注意:2048位的RSA金鑰,加密塊長度也必須是2048位,也就是256個位元組。所以示例中的加密塊需要填充202個FF才夠256個位元組。
故簽名的時候, 加密的塊: 00+01+FF(202個) +00 + ">3031300d060960864801650304020105000420b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
三、測試題
最後拋了兩個問題,看看大家有沒有理解上面所介紹的內容。
1、RSA簽名的時候 值是固定, 公鑰加密的結果確實隨機的,為什麼?
2、分析如下程式碼,是否有問題?