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.


2 Answers 2


What's going wrong here?

The digest of the DER encoding of SEQUENCE | INTEGER(n) | INTEGER(e) (https://www.rfc-editor.org/rfc/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://www.rfc-editor.org/rfc/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)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .