How to generate a tor service onion address from the public key in java?

I am trying to generate the onion address that is generated from a public key.

If the following line is added to the code in a previous post, just after privateKeyEncoded

String publicKeyEncoded = encoder.encodeToString(publicKey.getEncoded());

When I put the privateKeyEncoded into the /var/lib/tor/hidden_service/private_key, save the publicKeyEncoded and start the tor service, a new onion address is created. I am trying to get the same onion address as the tor service and from one created from the publicKeyEncoded. Using this code

import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;

//base64 string from the public key created
String publicKeyTest = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMnFkJTMZ2ZxnqLwCiB/EWHjsHbnC+sKEIrGbyOTYiTl3LygsekAX6LhgcllscLUFqSKlMRB3jRB0GAPrIc73E/hTnmWBtF8NT8DhZzl06LZ1BtNjfON1pHm87STMAayiSaXPmSOwIqOA89aJPcA9m4v4IhtjYSFXmCAsE4RqoAwIDAQAB";
//the onion address the tor service gives when the private key is used
String onionAddressTest = "qqkhrc4men3fiqyl";

byte[] publicKeyDecoded = Base64.decodeBase64(publicKeyTest);
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
byte[] sha1hash = messageDigest.digest(publicKeyDecoded);
int numberOfCharacters = 10;
byte[] reducedHash = new byte[numberOfCharacters];
for(int i = 0; i < numberOfCharacters; i++) {
    reducedHash[i] = sha1hash[i];
}
Base32 base32encoder = new Base32();
String onionAddress = base32encoder.encodeAsString(reducedHash).toLowerCase();
System.out.println(onionAddress);  // but this gives "7j3iz4of464hje2e"

I've tried using spongycastle to replicate my conversion but get the same answer. Which makes me think there's something wrong with how I generate the public key or there's something wrong in my initial conversion from base64.

Given the public key (publicKeyTest) how can you get the onion address (onionAddressTest) using java?

1 answer

  • answered 2018-04-14 15:47 dave_thompson_085

    According to this and this you need to hash only the part starting at offset 22 of the X.509 SubjectPublicKeyInfo encoding used by Java which calls it 'X.509' and by OpenSSL which calls it 'PUBKEY'. I can't find any actual Tor doc on this, but I don't believe it can be accidental that this is exactly the beginning of the algorithm-dependent data in SPKI format for an RSA-1024 key:

    $ openssl asn1parse -i <49833260.b64
        0:d=0  hl=3 l= 159 cons: SEQUENCE
        3:d=1  hl=2 l=  13 cons:  SEQUENCE
        5:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
       16:d=2  hl=2 l=   0 prim:   NULL
       18:d=1  hl=3 l= 141 prim:  BIT STRING
    # 18 +3 for DER tag+len +1 for unusedbitcount in BITSTRING = 22
    # and the content beginning at 22 is:
    $ openssl asn1parse -i -strparse 22 <49833260.b64
        0:d=0  hl=3 l= 137 cons: SEQUENCE
        3:d=1  hl=3 l= 129 prim:  INTEGER           :8C9C59094CC6766719EA2F00A207F11
    61E3B076E70BEB0A108AC66F23936224E5DCBCA0B1E9005FA2E181C965B1C2D416A48A94C441DE34
    41D0600FAC873BDC4FE14E799606D17C353F03859CE5D3A2D9D41B4D8DF38DD691E6F3B4933006B2
    8926973E648EC08A8E03CF5A24F700F66E2FE0886D8D84855E6080B04E11AA803
      135:d=1  hl=2 l=   3 prim:  INTEGER           :010001
    # which is (exactly) the RSAPublicKey structure from PKCS1
    

    So to do this in Java you can just assume RSA-1024, or with BouncyCastle (and I assume, but haven't tested, spongycastle as well) you can actually parse the ASN.1 properly:

    byte[] pubkeyder = Base64.decode("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMnFkJTMZ2ZxnqLwCiB/EWHjsHbnC+sKEIrGbyOTYiTl3LygsekAX6LhgcllscLUFqSKlMRB3jRB0GAPrIc73E/hTnmWBtF8NT8DhZzl06LZ1BtNjfON1pHm87STMAayiSaXPmSOwIqOA89aJPcA9m4v4IhtjYSFXmCAsE4RqoAwIDAQAB");
    MessageDigest sha1 = MessageDigest.getInstance("SHA1");
    // method 1
    byte[] x1 = sha1.digest (Arrays.copyOfRange(pubkeyder, 22, pubkeyder.length));
    System.out.println (new String(b32enc(Arrays.copyOf(x1,10))).toLowerCase());
    // method 2
    byte[] x2 = sha1.digest (SubjectPublicKeyInfo.getInstance(pubkeyder).getPublicKeyData().getOctets());
    System.out.println (new String(b32enc(Arrays.copyOf(x2,10))).toLowerCase());
    -> 
    qqkhrc4men3fiqyl
    qqkhrc4men3fiqyl