2016-11-21 67 views
1

我一直在爲此奮鬥了幾天。我正在研究在嵌入式Linux環境中運行的Java 1.7應用程序。 OpenSSL不可用,我無法控制設備上操作系統映像中的內容。我需要計算自簽名X.509證書的主題散列,產生與OpenSSL 1.0+相同的結果。這個現有的回答讓我開始:Java 1.7主題Hash of X.509證書OpenSSL 1.0+兼容

The new subject hash openssl algorithm differs

從我的測試應用程序的代碼如下所示。我的計算適用於主題名稱僅包含CN值的證書,但對於指定了任何其他主題組件(OU,O,L,ST或C)的證書不起作用。對於那些證書,整個主題上的散列(不包括前奏序列)不匹配。根據上面的答案,我已經提取了每個組件(使用getObjectAt()方法),並單獨對每個組件進行散列處理(沒有快樂),顛倒了它們的順序並且散列了它們(沒有喜悅),以及其他一些變體主題。我一直在努力避免我擔心的是下載OpenSSL源代碼並使其運行更耗時,因此我可以檢查中間結果並查看出錯的地方。也許有人這樣做可以提供一些指導。

private static void getSubjectHash(X509Certificate x509Cert) 
{ 
    try { 
     // get the subject principal 
     X500Principal x500Princ = x509Cert.getSubjectX500Principal(); 

     // create a new principal using canonical name (order, spacing, etc.) and get it in ANS1 DER format 
     byte[] newPrincEnc = new X500Principal(x500Princ.getName(X500Principal.CANONICAL)).getEncoded(); 

     // read it in as an ASN1 Sequence to avoid custom parsing 
     ASN1InputStream aIn = new ASN1InputStream(newPrincEnc); 
     ASN1Sequence seq = (ASN1Sequence) aIn.readObject(); 

     List<byte[]> terms = new ArrayList<>(); 
     int finalLen = 0; 
     int i = 0; 

     // hash the encodables for each term individually and accumulate them in a list 
     for (ASN1Encodable asn1Set : seq.toArray()) { 
      byte[] term = ((ASN1Set) asn1Set).getEncoded(); 
      terms.add(term); 
      finalLen += term.length; 

      // digest the term 
      byte[] hashBytes = truncatedHash(getDigest(term), 4); 
      printByteArray(String.format("hash of object at %d:", i++), hashBytes); 

      System.out.println(""); 
     } 


     // hash all terms together in order of appearance 
     int j = 0; 
     byte[] finalEncForw = new byte[finalLen]; 
     for (byte[] term : terms) 
      for (byte b : term) 
       finalEncForw[j++] = b; 

     // digest and truncate 
     byte[] hashBytes = truncatedHash(getDigest(finalEncForw), 4); 

     printByteArray("hash of all terms in forward order", hashBytes); 
     System.out.println(""); 


     // hash all terms together in reverse order 
     j = 0; 
     byte[] finalEncRev = new byte[finalLen]; 
     for (int k = terms.size() - 1; k >= 0; --k) 
      for (byte b : terms.get(k)) 
       finalEncRev[j++] = b; 

     // digest and truncate 
     hashBytes = truncatedHash(getDigest(finalEncRev), 4); 

     printByteArray("hash of all terms in reverse order", hashBytes); 
    } 
    catch (Exception ex) { 
     throw new RuntimeException("uh-oh"); 
    } 
} 

private static byte[] getDigest(byte[] toHash) 
{ 
    MessageDigest md; 

    try { 
     md = MessageDigest.getInstance("SHA1"); 
    } 
    catch (NoSuchAlgorithmException nsa) { 
     throw new RuntimeException("no such algorithm"); 
    } 

    return md.digest(toHash); 
} 

private static byte[] truncatedHash(byte[] hash, int truncatedLength) 
{ 
    if (truncatedLength < 1 || hash.length < 1) 
     return new byte[0]; 

    byte[] result = new byte[truncatedLength]; 

    for (int i = 0; i < truncatedLength; ++i) 
     result[truncatedLength - 1 - i] = hash[i]; 

    return result; 
} 

private static void printByteArray(String name, byte[] bytes) 
{ 
    System.out.println(name + " length=" + String.valueOf(bytes.length)); 
    for (byte b: bytes) { 
     System.out.print(String.format("%02X ", Byte.toUnsignedInt(b))); 
    } 

    System.out.println(); 
} 
+0

構建OpenSSL。我的openssl和DER編碼的數據略有不同。看來,openssl將每個主題名稱條目標記爲0x0C而不是0x13(可打印的字符串)。儘管如此,0x0C並沒有出現在X.208允許標籤的表格中,所以我不確定這是什麼意思。我使用上面的標籤CN作爲0x0C,但所有其他主題名稱條目爲0x13的ASN1庫。這就是爲什麼當CN以外的項目存在時,我不匹配openssl的原因。我可以用膠帶粘貼,但我想明白爲什麼。規格更新的MOD? – NefariousB

回答

1

好吧,現在是膠帶。這似乎適用於我可以測試的所有證書。這是getSubjectHash方法的重寫版本:

private static void getSubjectHash(X509Certificate x509Cert) 
{ 
    try { 
     // get the subject principal 
     X500Principal x500Princ = x509Cert.getSubjectX500Principal(); 

     // create a new principal using canonical name (order, spacing, etc.) and get it in ANS1 DER format 
     byte[] newPrincEnc = new X500Principal(x500Princ.getName(X500Principal.CANONICAL)).getEncoded(); 

     // read it in as an ASN1 Sequence to avoid custom parsing 
     ASN1InputStream aIn = new ASN1InputStream(newPrincEnc); 
     ASN1Sequence seq = (ASN1Sequence) aIn.readObject(); 

     List<byte[]> terms = new ArrayList<>(); 
     int finalLen = 0; 
     int i = 0; 

     // hash the encodables for each term individually and accumulate them in a list 
     for (ASN1Encodable asn1Set : seq.toArray()) { 
      byte[] term = ((ASN1Set) asn1Set).getEncoded(); 
      term[9] = 0x0c; // change tag from 0x13 (printable string) to 0x0c 
      terms.add(term); 
      finalLen += term.length; 

      // digest the term 
      byte[] hashBytes = truncatedHash(getDigest(term), 4); 
      printByteArray(String.format("hash of object at %d:", i++), hashBytes); 

      System.out.println(""); 
     } 


     // hash all terms together in order of appearance 
     int j = 0; 
     byte[] finalEncForw = new byte[finalLen]; 
     for (byte[] term : terms) 
      for (byte b : term) 
       finalEncForw[j++] = b; 

     // digest and truncate 
     byte[] hashBytes = truncatedHash(getDigest(finalEncForw), 4); 

     printByteArray("hash of all terms in forward order", hashBytes); 
     System.out.println(""); 

    } 
    catch (Exception ex) { 
     throw new RuntimeException("uh-oh"); 
    } 
} 
+0

標記0x0C是UTF8String - RFC2459 – NefariousB

0

感謝您的代碼花花公子。我對它進行了改進,以支持在主題中包含擴展ASCII字符的證書(例如,éËÁñç)。

public static int X509_NAME_hash(X509Certificate x509Cert) throws IOException, NoSuchAlgorithmException { 
    // get the subject principal 
    X500Principal x500Princ = x509Cert.getSubjectX500Principal(); 
    byte[] newPrincEnc = x500Princ.getEncoded(); 
    final ASN1Sequence asn1Sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(newPrincEnc); 

    Debugger.log(asn1Sequence); 

    List<byte[]> terms = new ArrayList<>(); 
    int finalLen = 0; 

    // hash the encodables for each term individually and accumulate them in a list 
    for (ASN1Encodable asn1Set : asn1Sequence.toArray()) { 
     byte[] term = ((ASN1Set) asn1Set).getEncoded(); 
     term[9] = 0x0c; // change tag from 0x13 (printable string) to 0x0c 

     for (int i = 11; i < term.length; i++) { 
      byte actual = term[i]; 
      //lowercase only if the character is not ASCCI Extended (below 126) 
      if (actual < 127) { 
       term[i] = (byte) Character.toLowerCase((char) actual); 
      } 
     } 

     terms.add(term); 
     finalLen += term.length; 
    } 

    // hash all terms together in order of appearance 
    int j = 0; 
    byte[] finalEncForw = new byte[finalLen]; 
    for (byte[] term : terms) 
     for (byte b : term) 
      finalEncForw[j++] = b; 

    return peekInt(MessageDigest.getInstance("SHA1").digest(finalEncForw), 0, ByteOrder.LITTLE_ENDIAN); 
} 

public static X509Certificate readCertificate(File rootFile) throws CertificateException, IOException { 
    CertificateFactory fact = CertificateFactory.getInstance("X.509"); 
    FileInputStream is = new FileInputStream(rootFile); 
    return (X509Certificate) fact.generateCertificate(is); 
} 

public static int peekInt(byte[] src, int offset, ByteOrder order) { 
    if (order == ByteOrder.BIG_ENDIAN) { 
     return (((src[offset++] & 0xff) << 24) | ((src[offset++] & 0xff) << 16) | ((src[offset++] & 0xff) << 8) 
       | ((src[offset] & 0xff) << 0)); 
    } else { 
     return (((src[offset++] & 0xff) << 0) | ((src[offset++] & 0xff) << 8) | ((src[offset++] & 0xff) << 16) 
       | ((src[offset] & 0xff) << 24)); 
    } 
}