-
-
Save artizirk/c91e4f8c237dec07e3ad1b286f1855a7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3 | |
# need at least python3.6+ for blake2 | |
from base64 import b64decode | |
from hashlib import sha256, blake2s | |
from ipaddress import ip_address, ip_network | |
# https://github.com/chmduquesne/wg-ip | |
def gen_ip(pubkey, subnet=ip_network('fe80::/64')): | |
"""Generate wg-ip compatible addresses from WireGuard public key. | |
Uses sha256 over base64 encoded newline terminated publickey string. | |
""" | |
prefix_bytes = subnet.network_address.packed | |
mask_bytes = subnet.netmask.packed | |
suffix_bytes = sha256(pubkey.encode('ascii')+b'\n').digest() | |
address = b'' | |
for prefix, suffix, mask in zip(prefix_bytes, suffix_bytes, mask_bytes): | |
address += ((prefix & mask) | (suffix & (mask^255))).to_bytes(1, byteorder='big') | |
return ip_address(address) | |
# https://gist.github.com/reidrankin/3a39210ce437680f5cf1ac549fd1f1ff | |
# https://gist.github.com/artizirk/fca1ba915e741d60ec4fec957dfe0859 | |
def gen_lla(pubkey, subnet=ip_network('fe80::/10')): | |
"""Calculates cryptographically-bound IPv6 Link-Local Addresses from WireGuard public keys. | |
Uses blake2s hash over public key as bytes. | |
""" | |
prefix_bytes = subnet.network_address.packed | |
mask_bytes = subnet.netmask.packed | |
suffix_bytes = blake2s(pubkey).digest() | |
address = b'' | |
for prefix, suffix, mask in zip(prefix_bytes, suffix_bytes, mask_bytes): | |
address += ((prefix & mask) | (suffix & (mask^255))).to_bytes(1, byteorder='big') | |
return ip_address(address) | |
# https://gist.github.com/artizirk/c91e4f8c237dec07e3ad1b286f1855a7?permalink_comment_id=5374168#gistcomment-5374168 | |
def gen_lla_no_hash(pubkey, subnet=ip_network('fe80::/10')): | |
"""Calculates IPv6 Link-Local Addresses from WireGuard public keys. | |
Uses public key bytes as the ip address. | |
""" | |
prefix_bytes = subnet.network_address.packed | |
mask_bytes = subnet.netmask.packed | |
address = b'' | |
for prefix, suffix, mask in zip(prefix_bytes, pubkey, mask_bytes): | |
address += ((prefix & mask) | (suffix & (mask^255))).to_bytes(1, byteorder='big') | |
return ip_address(address) | |
if __name__ == '__main__': | |
print("# sha256 based address calculation") | |
pubkey = "foo" | |
subnet = ip_network("fd1a:6126:2887::/48") | |
exp_result = ip_address("fd1a:6126:2887:f9b1:d61e:21e7:96d7:8dcc") | |
real_result = gen_ip(pubkey, subnet) | |
print(exp_result, real_result, exp_result==real_result) | |
print("# blake2s based address calculation") | |
pubkey = b"\x00"*32 # 32byte public key fo all zeroes | |
exp_result = ip_address("fe8b:5ea9:9e65:3bc2:b593:db41:30d1:0a4e") | |
real_result = gen_lla(pubkey) | |
print(exp_result, real_result, exp_result==real_result) | |
pubkey = b64decode("hTvlXzX5ZoTg6BxbWHYWSZo6pkLGPdQwVoXZYqrMMgs=") | |
exp_result = ip_address("fe80:3fe9:4d26:adc4:1953:7b20:314a:3167") | |
real_result = gen_lla(pubkey) | |
print(exp_result, real_result, exp_result==real_result) | |
print("# address directly from pubkey") | |
pubkey = b"\x00"*32 # Fake pubkey | |
exp_result = ip_address("fe80::") | |
real_result = gen_lla_no_hash(pubkey) | |
print(exp_result, real_result, exp_result==real_result) | |
pubkey = b64decode("hTvlXzX5ZoTg6BxbWHYWSZo6pkLGPdQwVoXZYqrMMgs=") | |
exp_result = ip_address("febb:e55f:35f9:6684:e0e8:1c5b:5876:1649") | |
real_result = gen_lla_no_hash(pubkey) | |
print(exp_result, real_result, exp_result==real_result) | |
pubkey = b64decode("hTvlXzX5ZoTg6BxbWHYWSZo6pkLGPdQwVoXZYqrMMgs=") | |
subnet = ip_network("fe80::/64") # AFAIk somewhere in the IP spec it was said that Link Local should be /64 | |
exp_result = ip_address("fe80::e0e8:1c5b:5876:1649") | |
real_result = gen_lla_no_hash(pubkey, subnet=subnet) | |
print(exp_result, real_result, exp_result==real_result) |
@AlexanderYastrebov thats just what the original algorithm author did. Looks like link there is gone but I managed to publish based on Wayback Machine snapshot https://gist.github.com/artizirk/fca1ba915e741d60ec4fec957dfe0859
I have also updated the gist to include a non hashed version of the IPv6 address generation that just uses public key bytes as the address. That could be used to generate some vanity ip addresses and keys using this https://github.com/warner/wireguard-vanity-address
Another thing is that IPv6 addresses are a lot smaller than WireGuard public keys so the chance of address collision is higher and having a hash between the public key and the address would allow for easy salting and generation of new unique addresses. blake2s is also the has that is used internally by wireguard and is simpler to implement and faster compared to sha256. (could be important in embedded usecases and keeps dependencies lower)
Why does gen_lla need blake2s given that publickey bytes are already random?
That could be used to generate some vanity ip addresses and keys using this https://github.com/warner/wireguard-vanity-address
Nice! Did not know about vanity keys (also found similar projects https://github.com/axllent/wireguard-vanity-keygen https://github.com/volleybus/Wireguard-Vanity-Key-Searcher https://github.com/thatsed/wgmine) - those are not random of course.
Thanks for the follow up!
Nice! Did not know about vanity keys
Nerd sniped, I've created a similar tool https://github.com/AlexanderYastrebov/wireguard-vanity-key 🤣
gen_ip uses sha256 because input is base64 text.
Why does gen_lla need blake2s given that publickey bytes are already random?