Tor v2 addresses were generated by a base32 string of the first 80 bits of the SHA1 hash of the private key (sic - public key)

How can I generate Tor v3 addresses? (basic command line tools preferred, openssl, ssh-keygen, base32 and so on)

Please add sample key.pub, (full, not first 2 bytes of) checksum, and resulting .onion address.


I have found a sample (encoded in hex).


3d3d 2065 6432 3535 3139 7631 2d70 7562
6c69 633a 2074 7970 6530 203d 3d00 0000
0044 6619 5537 af38 e2c6 7ac2 d8f6 c13f
958f 7e63 41d0 60e2 727f ca7b a285 722b


3d3d 2065 6432 3535 3139 7631 2d73 6563
7265 743a 2074 7970 6530 203d 3d00 0000
40ff f076 1f62 2657 85e9 0ec8 a2f9 7c52
1a6f 97ab bc20 2bd8 13e6 7442 7d67 506c
27aa 4c9b 935a 15c9 891d f2b0 e2a2 8acd
68fc 8bc8 a483 2829 7f93 9de4 f714 cb59

Resulting address
(do not use this address - the private key is now public)


https://gitweb.torproject.org/torspec.git/tree/rend-spec-v3.txt#n2013 (links to the correct line as of today. If you're from the future, you might need to ctrl-f for "ONIONADDRESS")

The onion address of a hidden service includes its identity public key, a version field and a basic checksum. All this information is then base32 encoded as shown below:

 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
 CHECKSUM = H(".onion checksum" | PUBKEY | VERSION)[:2]

   - PUBKEY is the 32 bytes ed25519 master pubkey of the hidden service.
   - VERSION is an one byte version field (default value '\x03')
   - ".onion checksum" is a constant string
   - CHECKSUM is truncated to two bytes before inserting it in onion_address

Here are a few example addresses:


For more information about this encoding, please see our discussion thread at [ONIONADDRESS-REFS].

| improve this answer | |
  • Truncate from left or right? Say hash is 123abc. Does this truncate to 12 or bc? – pnujhpdo Apr 26 '18 at 23:39
  • cat my_key.pub will not work, no. Look at the contents of the file. It isn't just the raw public key. The hash function is sha3_256. The truncation only keeps the first two bytes. You are not going to have a fun time trying to do this with a bash script. You should use a language that makes manipulating bytes easy. – pastly Apr 27 '18 at 12:44
  • if one wonders what the pipe (|) in the specs means, see this – Mr. Jul 6 '19 at 13:40
  • What is the H() function used to create the checksum? – Joakim L. Christiansen Sep 21 '19 at 20:41

So it seems the public key is only 32 bytes long, but in your example it is 64 bytes long. I coded the following script, given the full public key of 64 bytes and got the sample address you put. So take it as a reference!

from base64 import b32encode
from hashlib import sha3_256

def main():
    # Expected result: abcgmgkvg6xtrywgplbnr5wbh6ky67tdihigbytsp7fhxiufoiv6w3yd.onion
    hex_string = "3d3d 2065 6432 3535 3139 7631 2d70 7562 6c69 633a 2074 7970 6530 203d 3d00 0000" + " 0044 6619 5537 af38 e2c6 7ac2 d8f6 c13f 958f 7e63 41d0 60e2 727f ca7b a285 722b"
    pub = bytearray.fromhex(hex_string)[32:]
    version = b"\x03"
    checksum = sha3_256(bytes(".onion checksum", "utf-8") + pub + version).digest()[:2]
    address = b32encode(pub + checksum + version).decode().lower()
    print(address + ".onion")

if __name__ == "__main__":
| improve this answer | |

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.