JAVA解析各種編碼密鑰對(DER、PEM、openssh公鑰)


一、DER編碼密鑰對

先說下DER編碼,是因為JCE本身是支持DER編碼密鑰對的解析的,可以參見PKCS8EncodedKeySpec和X509EncodedKeySpec.

DER編碼是ASN.1編碼規則中的一個子集,具體格式如何編排沒有去了解,但最終呈現肯定的是一堆有規律的二進制組合而成。

PKCS#8定義了私鑰信息語法和加密私鑰語法,而X509定義證書規范,通常都會用DER和PEM進行編碼存儲,而在JAVA中則使用的

DER。

 

接下來看看如果通過DER編碼的密鑰對分別獲取JAVA的公私鑰對象。

 

1.下面一段是生成私鑰對象的,傳入參數是DER編碼的私鑰內容。

 

[java]  view plain copy print ?
 
  1. @Override  
  2. public PrivateKey generatePrivateKey(byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException {  
  3.     KeySpec keySpec = new PKCS8EncodedKeySpec(key);  
  4.     KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
  5.     return keyFactory.generatePrivate(keySpec);  
  6. }  


2.下面是生成公鑰對象的,傳入參數是DER編碼公鑰內容,可以看到和生成私鑰的部分非常相似。

 

 

[java]  view plain copy print ?
 
  1. public PublicKey geneneratePublicKey(byte[] key) throws InvalidKeySpecException, NoSuchAlgorithmException{  
  2.     KeySpec keySpec = new X509EncodedKeySpec(key);  
  3.     KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
  4.     return keyFactory.generatePublic(keySpec);  
  5. }  

 

 

二、PEM編碼

PEM編碼也是密鑰對較常用的編碼方式,openssl則是以PEM編碼為主,相對DER對人可讀性更強,以BASE64編碼呈現,外圍包上類似-----BEGIN RSA PRIVATE KEY-----。

 

JCE沒有對PEM直接支持的方式,但是可以通過第三方包例如bouncycastle解析,當然如果想要自己理解pem編碼結構,也可以自己寫代碼解析。

 

這里介紹下如何使用bouncycastle進行解析。

 

[java]  view plain copy print ?
 
  1.     FileInputStream fis = new FileInputStream("id_rsa");  
  2.     byte[] key = PrivateKeyUtils.readStreamToBytes(fis);  
  3. Security.addProvider(new BouncyCastleProvider());  
  4. ByteArrayInputStream bais = new ByteArrayInputStream(key);  
  5. PEMReader reader = new PEMReader(new InputStreamReader(bais), new PasswordFinder() {  
  6.       
  7.     @Override  
  8.     public char[] getPassword() {  
  9.         return "".toCharArray();  
  10.     }  
  11. });  
  12. KeyPair keyPair = (KeyPair) reader.readObject();  
  13. reader.close();  
  14. PublicKey pubk = keyPair.getPublic();  
  15. System.out.println(pubk);  
  16. PrivateKey prik = keyPair.getPrivate();  
  17. System.out.println(prik);  
  18.   
  19. KeySpec keySpec = new X509EncodedKeySpec(pubk.getEncoded());  
  20. KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
  21. System.out.println(keyFactory.generatePublic(keySpec));  
  22.   
  23. KeySpec keySpec2 = new PKCS8EncodedKeySpec(prik.getEncoded());  
  24. System.out.println(keyFactory.generatePrivate(keySpec2));  



 

 
        

 

 

看下這個輸出結果

 

[plain]  view plain copy print ?
 
  1. RSA Public Key  
  2.             modulus: c8f3e2d2e7fffe049127a115cab648fa9f55a7712d40868dccbddef9ebf030480a31f060e6c1ace2c53660e467cd173870367223dccea00ef2bdf6795757eb34fe1e8cfb63a0d333eefc9739029512df68108dd4b8054a12bdb206abd2ee7a727faa79604680c1337473ecd3d9a1a10b6cbc3af7862a74619ea7fe3a5bb2b89dded41dc5e4e4d5fcad169b85a599487929de1788e1e9a8d4efae4fda811d1e4d975b50d0d61b5887402ca975ec5e1d3ff193522b84746fe35ab00d073fed466786d9303f19c642c02cb1ad3a1ec6f0b7234e492e79500ee0bb1c1f07c23ae70af9b75aa35a6c75573d302cbf8f034341932dc371689b9f952388328c5277c117  
  3.     public exponent: 10001  
  4.   
  5. RSA Private CRT Key  
  6.             modulus: c8f3e2d2e7fffe049127a115cab648fa9f55a7712d40868dccbddef9ebf030480a31f060e6c1ace2c53660e467cd173870367223dccea00ef2bdf6795757eb34fe1e8cfb63a0d333eefc9739029512df68108dd4b8054a12bdb206abd2ee7a727faa79604680c1337473ecd3d9a1a10b6cbc3af7862a74619ea7fe3a5bb2b89dded41dc5e4e4d5fcad169b85a599487929de1788e1e9a8d4efae4fda811d1e4d975b50d0d61b5887402ca975ec5e1d3ff193522b84746fe35ab00d073fed466786d9303f19c642c02cb1ad3a1ec6f0b7234e492e79500ee0bb1c1f07c23ae70af9b75aa35a6c75573d302cbf8f034341932dc371689b9f952388328c5277c117  
  7.     public exponent: 10001  
  8.    xxx  
  9.   
  10. Sun RSA public key, 2048 bits  
  11.   modulus: 25367925677263221630752072905429434117596189021449325931333193529363239091429133073657699480437320802724298965580526553453499379398405915207286949216370963889754756788008021698178495726807109888833039800230667805051637457878962812581009778614579379073430749907762728841603230968432191178635884450213875555645164935313884823663096624318071901833679005494934145072511042211644746801428698070096755012497436134537077746175344235590315572214836519284172251946833712681076781034466422251569387242330311670205489884189790153154281087401570994337126054046621401176808489895271448688335849540690562754961439975230588159770903  
  12.   public exponent: 65537  
  13. Sun RSA private CRT key, 2048 bits  
  14.   modulus:          25367925677263221630752072905429434117596189021449325931333193529363239091429133073657699480437320802724298965580526553453499379398405915207286949216370963889754756788008021698178495726807109888833039800230667805051637457878962812581009778614579379073430749907762728841603230968432191178635884450213875555645164935313884823663096624318071901833679005494934145072511042211644746801428698070096755012497436134537077746175344235590315572214836519284172251946833712681076781034466422251569387242330311670205489884189790153154281087401570994337126054046621401176808489895271448688335849540690562754961439975230588159770903  
  15.   public exponent:  65537  
  16.   xxx  

中間內容太多,略去了一部分,看下重點的public exponent部分,發現不同,但其實是一個是10進制輸出,一個是16進制輸出,所以在這里提個醒,這里生成過程沒有錯的。

 

 

三、openssh公鑰

很多SSH公鑰習慣使用openssh格式的,下面介紹下openssh格式的公鑰如何解析,目前好像是沒有官方庫或者第三方庫提供支持的。

openssh公鑰呈現形式如

 

[plain]  view plain copy print ?
 
  1. ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCW6qYq6m8gVOWLyTB1JGl1aLrJDOCIfErXWNUsNeUXg4UdAtSbkiA+Ta9Nx6oMR4w+OkPbxyivnzkZt1YpmDxrm1z99z81/VyVw+lue+3neRjTgfGMascG+46b7DpEKLXlfS2hwOA+4ooRIeR+LbQZVovy5SP6ZTngskiqcySYqQ== RSA-1024  



以ssh-rsa打頭,描述“RSA-1024”結尾的形式,中間是Base64編碼。

 

這里過濾掉除了Base64外的其他部分,解碼Base64得到公鑰二進制內容。

這里二進制編碼格式如下:

前11字節固定

0 0 0 7  's' 's' 'h' '-' ‘r' 's' 'a' 

緊接着4個字節為一個int值,表示public exponent所占字節長度

可通過移位符及相加或者BigInteger方式實現轉換。

 

[java]  view plain copy print ?
 
  1.     public static int decodeUInt32(byte[] key, int start_index){  
  2.         byte[] test = Arrays.copyOfRange(key, start_index, start_index + 4);  
  3.         return new BigInteger(test).intValue();  
  4. //      int int_24 = (key[start_index++] << 24) & 0xff;  
  5. //      int int_16 = (key[start_index++] << 16) & 0xff;  
  6. //      int int_8 = (key[start_index++] << 8) & 0xff;  
  7. //      int int_0 = key[start_index++] & 0xff;  
  8. //      return int_24 + int_16 + int_8 + int_0;  
  9.     }  


得到長度后,再從后一字節開始讀取該長度字節作為public exponent的值,構造相應BigInteger。

 

 

再緊接着4個字節也是一個int值,表示modulus所占字節長度,同理轉換得到長度。

再根據長度讀取字節數組得到modulus值即可。

 

相應代碼如下

 

[java]  view plain copy print ?
 
  1. public static RSAPublicKey decodePublicKey(byte[] key) throws NoSuchAlgorithmException, InvalidKeySpecException{  
  2.     byte[] sshrsa = new byte[] { 0007's''s''h''-''r''s',  
  3.     'a' };  
  4.     int start_index = sshrsa.length;  
  5.     /* Decode the public exponent */  
  6.     int len = decodeUInt32(key, start_index);  
  7.     start_index += 4;  
  8.     byte[] pe_b = new byte[len];  
  9.     for(int i= 0 ; i < len; i++){  
  10.         pe_b[i] = key[start_index++];  
  11.     }  
  12.     BigInteger pe = new BigInteger(pe_b);  
  13.     /* Decode the modulus */  
  14.     len = decodeUInt32(key, start_index);  
  15.     start_index += 4;  
  16.     byte[] md_b = new byte[len];  
  17.     for(int i = 0 ; i < len; i++){  
  18.         md_b[i] = key[start_index++];  
  19.     }  
  20.     BigInteger md = new BigInteger(md_b);  
  21.     KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
  22.     KeySpec ks = new RSAPublicKeySpec(md, pe);  
  23.     return (RSAPublicKey) keyFactory.generatePublic(ks);  
  24. }  



 

四、其他編碼

后續有機會再研究其他編碼方式如何解析,不過可能bouncycastle已經提供了許多編碼的解析,可以直接使用,具體沒看,有興趣的可以研究下。
 
下面有個網站介紹各種編碼的,還有利用openssl進行各種轉換的


免責聲明!

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



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