-
-
Save ryancdotorg/9bd3873e488740f86ebb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# I don't know what happened here - the gist history is somehow screwed up. My original is here: | |
# https://gist.github.com/ryancdotorg/18235723e926be0afbdd | |
""" | |
sudo apt-get install python-gmpy python-m2crypto python-crypto python-dev | |
sudo pip install pycurve25519 | |
./rsabd.py >poc.key | |
openssl req -new -key poc.key -out poc.csr | |
openssl x509 -req -days 365 -in poc.csr -signkey poc.key -out poc.crt | |
./rsabd.py '' poc.crt | |
""" | |
import sys | |
import gmpy | |
import curve25519 | |
from struct import pack | |
from hashlib import sha256 | |
from binascii import hexlify, unhexlify | |
from M2Crypto import X509 | |
from Crypto.Cipher import AES | |
from Crypto.PublicKey import RSA | |
MASTER_PUB_HEX = 'ce75b4ddd279d5f8009b454fa0f025861b65ebfbe2ada0823e9d5f6c3a15cf58' | |
# A simple AES-CTR based CSPRNG, not particularly interesting | |
class AESPRNG(object): | |
def __init__(self, seed): | |
key = sha256(seed).digest() | |
self.buf = '' | |
self.buf_left = 0 | |
self.counter = 0 | |
self.cipher = AES.new(key, AES.MODE_ECB) | |
def randbytes(self, n): | |
ret = '' | |
requested = n | |
while requested > 0: | |
# Grab all unused bytes in the buffer and then refill it | |
if requested > self.buf_left: | |
ret += self.buf[(16-self.buf_left):] | |
requested -= self.buf_left | |
# Encrypt the big-endian counter value for | |
# the next block of pseudorandom bytes | |
self.buf = self.cipher.encrypt(pack('>QQ', 0, self.counter)) | |
self.counter += 1 | |
self.buf_left = 16 | |
# Grab only the bytes we need from the buffer | |
else: | |
ret += self.buf[(16-self.buf_left):(16-self.buf_left+requested)] | |
self.buf_left -= requested | |
requested = 0 | |
return ret | |
# overwrite some bytes in orig at a specificed offset | |
def replace_at(orig, replace, offset): | |
return orig[0:offset] + replace + orig[offset+len(replace):] | |
def build_key(bits=2048, e=65537, embed='', pos=1, randfunc=None): | |
# generate base key | |
rsa = RSA.generate(bits, randfunc) | |
# extract modulus as a string | |
n_str = unhexlify(str(hex(rsa.n))[2:-1]) | |
# embed data into the modulus | |
n_hex = hexlify(replace_at(n_str, embed, pos)) | |
n = gmpy.mpz(n_hex, 16) | |
p = rsa.p | |
# compute a starting point to look for a new q value | |
pre_q = n / p | |
# use the next prime as the new q value | |
q = pre_q.next_prime() | |
n = p * q | |
phi = (p-1) * (q-1) | |
# compute new private exponent | |
d = gmpy.invert(e, phi) | |
# make sure that p is smaller than q | |
if p > q: | |
(p, q) = (q, p) | |
return RSA.construct((long(n), long(e), long(d), long(p), long(q))) | |
def recover_seed(key='', modulus=None, pos=1): | |
# recreate the master private key from the passphrase | |
master = bytes(sha256(key).digest()) | |
# extract the ephemeral public key from modulus | |
ephem_pub = modulus[pos:pos+32] | |
# compute seed with master private and ephemeral public | |
return (curve25519.shared(master,ephem_pub), ephem_pub) | |
if __name__ == "__main__": | |
# passphrase and filename as arguments | |
if len(sys.argv) == 3: | |
# Load an x.509 certificate from a file | |
x509 = X509.load_cert(sys.argv[2]) | |
# Pull the modulus out of the certificate | |
orig_modulus = unhexlify(x509.get_pubkey().get_modulus()) | |
(seed, ephem_pub) = recover_seed(key=sys.argv[1], modulus=orig_modulus, pos=80) | |
# no arguments, just generate a private key | |
else: | |
# deserialize master ECDH public key embedded in program | |
master_pub = bytes(unhexlify(MASTER_PUB_HEX)) | |
# generate a random (yes, actually random) ECDH private key | |
ephem = curve25519.genkey() | |
# derive the corresponding public key for later embedding | |
ephem_pub = curve25519.public(ephem) | |
# combine the ECDH keys to generate the seed | |
seed = curve25519.shared(ephem,master_pub) | |
prng = AESPRNG(seed) | |
ephem_pub = bytes(ephem_pub) | |
# deterministic key generation from seed | |
rsa = build_key(embed=ephem_pub, pos=80, randfunc=prng.randbytes) | |
if 'orig_modulus' in locals(): | |
if long(hexlify(orig_modulus), 16) != long(rsa.n): | |
raise Exception("key recovery failed") | |
print rsa.exportKey() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment