我已经为此苦苦挣扎了几天。我正在开发一个在嵌入式 Linux 环境中运行的 Java 1.7 应用程序。 OpenSSL 不可用,我无法控制设备上操作系统映像中的内容。我需要计算自签名 X.509 证书的主题哈希,生成与 OpenSSL 1.0+ 相同的结果。这个现有的答案让我开始:
新的主题散列 openssl 算法有所不同 https://stackoverflow.com/questions/19237167/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( );
}