By all sources I can find, onion / hidden service "domains" are generated by this process:

Generate a 1024-bit RSA keypair
Take the SHA-1 of the public key
Base32 encode the first 80 bytes of the hash

I have tried to replicate it, by first generating a memorable vanity URL with Scallion, "zzzzzzycizaamf47.onion". I then wrote a PHP script to derive the public key from that private key, strip the formatting, hash it, truncate to 10 chars, and base32 encode it. First of all I found three different "encode as base32" functions, all which give different results. This is the code, with results commented after the three functions:

$privKey = 'MIIEpAIBAAKC ~snip~ azScNv5A';
$rsa = new Crypt_RSA();
$pubKey = $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

$pubKey = str_replace(array("\r", "\n", ' ', '=', '-----BEGINRSAPUBLICKEY-----', 
    '-----ENDRSAPUBLICKEY-----'), '', $pubKey); //

$pubKey = sha1($pubKey);
$pubKey = substr($pubKey, 0, 10);

echo base_convert($pubKey, 16, 32).PHP_EOL; //Result: 68s3ii2

echo crockford32_encode($pubKey).PHP_EOL; //Result: 60rkjcb365hp2d1j

echo $base32->encode($pubKey).PHP_EOL; //Result: GAYTSMLDGFRWCNBS

What's going wrong here? Why do the three "base32" functions give different results? Here is the code for crockford32_encode, and here is the code for Base2n. I have tried nearly all combinations of formatting / whitespace stripping and character truncation, and have never gotten close to the desired "zzzzzzycizaamf47". I previously posted this question on StackOverflow and got no answers, only a comment suggesting I ask here.


What's going wrong here?

The digest of the DER encoding of SEQUENCE | INTEGER(n) | INTEGER(e) (https://tools.ietf.org/html/rfc3447#appendix-A.1.1, ITU-T X.690), not the Base64 encoded DER encoding.

Tor uses Base32 encoding as standardized by the IETF (https://tools.ietf.org/html/rfc4648) and not Douglas Crockford's variant.


Thanks @Yawning Angel, your answer helped, also the answer in this question, the code:

function privkey2pubkey($privKey) {
    $privKey = str_replace(array("\r", "\n", ' ', '=', '-----BEGINRSAPRIVATEKEY-----', 
    '-----ENDRSAPRIVATEKEY-----'), '', $privKey); 
    $rsa = new Crypt_RSA(); $rsa->loadKey($privKey);
    return $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

function pubkey2addr($pubKey) {
    $pubKey = str_replace(array("\r", "\n", ' ', '=', '-----BEGINRSAPUBLICKEY-----', 
    '-----ENDRSAPUBLICKEY-----'), '', $pubKey); 
    $pubKey = base64_decode($pubKey);
    $pubKey = sha1($pubKey, true);
    $pubKey = substr($pubKey, 0, 10);
    $base32 = new Base2n(5, 'abvdefghijklmnopqrstuvwxyz234567', FALSE, TRUE, TRUE);
    $pubKey = $base32->encode($pubKey).PHP_EOL;
    return $pubKey;

Requires phpseclib and Base2n. (I am the question asker, I just can't figure out how to log into that account, I must have 20+ stackexchange accounts by now)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.