def crypto_generichash(message, key, out_len=crypto_generichash_BYTES): #if out_len < crypto_generichash_BYTES_MIN: #raise CryptoError("out_len length shorter than crypto_generichash_BYTES_MIN") if out_len > crypto_generichash_BYTES_MAX: raise CryptoError("out_len length longer than crypto_generichash_BYTES_MAX") if not isinstance(message, (str)): raise CryptoError("message must be str") message_len = len(message) if not key: key = lib.ffi.NULL key_len = 0 else: key_len = len(key) if key_len < crypto_generichash_KEYBYTES_MIN: raise CryptoError("key length shorter than crypto_generichash_keybytes_min") if key_len > crypto_generichash_KEYBYTES_MAX: raise CryptoError("key length longer than crypto_generichash_keybytes_max") out = lib.ffi.new("unsigned char[]", out_len) if lib.crypto_generichash(out, out_len, message, message_len, key, key_len) != 0: raise CryptoError("An error occurred while crypto_generichash") return lib.ffi.buffer(out, out_len)[:]
def sodium_init(): """ Initializes sodium, picking the best implementations available for this machine. """ if lib.sodium_init() != 0: raise CryptoError("Could not initialize sodium")
def crypto_secretbox_open(key, ciphertext, nonce): """ Decrypt and returns the encrypted message ``ciphertext`` with the secret ``key`` and the nonce ``nonce``. :param key: bytes :param ciphertext: bytes :param nonce: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: raise ValueError("Invalid key") if len(nonce) != crypto_secretbox_NONCEBYTES: raise ValueError("Invalid nonce") padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext plaintext = lib.ffi.new("unsigned char[]", len(padded)) if lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key) != 0: raise CryptoError("Decryption failed. Ciphertext failed verification") plaintext = lib.ffi.buffer(plaintext, len(padded)) return plaintext[crypto_secretbox_ZEROBYTES:]
def crypto_box_open(ciphertext, nonce, pk, sk): """ Decrypts and returns an encrypted message ``ciphertext``, using the secret key ``sk``, public key ``pk``, and the nonce ``nonce``. :param ciphertext: bytes :param nonce: bytes :param pk: bytes :param sk: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise ValueError("Invalid nonce size") if len(pk) != crypto_box_PUBLICKEYBYTES: raise ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise ValueError("Invalid secret key") padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = ffi.new("unsigned char[]", len(padded)) if lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk) != 0: raise CryptoError("An error occurred trying to decrypt the message") return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
def _get_skpk_from_decrypted_private_blob(blob): checkint1 = blob.read(4) checkint2 = blob.read(4) if checkint1 != checkint2: LOG.error('Check: %s != %s', checkint1, checkint2) raise CryptoError() # We should parse n keys, but n is 1 decode_string(blob) # ignore key name decode_string(blob) # ignore pubkey skpk = decode_string(blob) LOG.debug('Private Key blob: %s', skpk.hex().upper()) assert len(skpk) == 64 sk = skpk[:32] # first half = priv key LOG.debug('ed25519 sk: %s', sk.hex().upper()) pk = skpk[32:] # second half = pub key LOG.debug('ed25519 pk: %s', pk.hex().upper()) seckey = crypto_sign_ed25519_sk_to_curve25519(skpk) LOG.debug('x25519 sk: %s', seckey.hex().upper()) pubkey = crypto_sign_ed25519_pk_to_curve25519(pk) LOG.debug('x25519 pk: %s', pubkey.hex().upper()) return (seckey, pubkey)
def crypto_hash_sha512(message): """ Hashes and returns the message ``message``. :param message: bytes :rtype: bytes """ digest = lib.ffi.new("unsigned char[]", crypto_hash_sha512_BYTES) if lib.crypto_hash_sha512(digest, message, len(message)) != 0: raise CryptoError("Hashing failed") return lib.ffi.buffer(digest, crypto_hash_sha512_BYTES)[:]
def crypto_shorthash(in_, k): if not in_: in_ = lib.ffi.new("unsigned char []", 1) in_len = 0 else: in_len = len(in_) out = lib.ffi.new("unsigned char []", crypto_shorthash_BYTES) if lib.crypto_shorthash(out, in_, in_len, k) != 0: raise CryptoError("An error occurred while crypto_shorthash") return lib.ffi.buffer(out, crypto_shorthash_BYTES)[:]
def crypto_scalarmult_base(n): """ Computes and returns the scalar product of a standard group element and an integer ``n``. :param n: bytes :rtype: bytes """ q = lib.ffi.new("unsigned char[]", crypto_scalarmult_BYTES) if lib.crypto_scalarmult_base(q, n) != 0: raise CryptoError( "An error occurred while computing the scalar product") return lib.ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:]
def crypto_sign(message, sk): """ Signs the message ``message`` using the secret key ``sk`` and returns the signed message. :param message: bytes :param sk: bytes :rtype: bytes """ signed = lib.ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES) signed_len = lib.ffi.new("unsigned long long *") if lib.crypto_sign(signed, signed_len, message, len(message), sk) != 0: raise CryptoError("Failed to sign the message") return lib.ffi.buffer(signed, signed_len[0])[:]
def crypto_sign_keypair(): """ Returns a randomly generated public key and secret key. :rtype: (bytes(public_key), bytes(secret_key)) """ pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) if lib.crypto_sign_keypair(pk, sk) != 0: raise CryptoError("An error occurred while generating keypairs") return ( lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], )
def crypto_sign_seed_keypair(seed): """ Computes and returns the public key and secret key using the seed ``seed``. :param seed: bytes :rtype: (bytes(public_key), bytes(secret_key)) """ if len(seed) != crypto_sign_SEEDBYTES: raise ValueError("Invalid seed") pk = lib.ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES) sk = lib.ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES) if lib.crypto_sign_seed_keypair(pk, sk, seed) != 0: raise CryptoError("An error occurred while generating keypairs") return ( lib.ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:], lib.ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:], )
def crypto_box_beforenm(pk, sk): """ Computes and returns the shared key for the public key ``pk`` and the secret key ``sk``. This can be used to speed up operations where the same set of keys is going to be used multiple times. :param pk: bytes :param sk: bytes :rtype: bytes """ if len(pk) != crypto_box_PUBLICKEYBYTES: raise ValueError("Invalid public key") if len(sk) != crypto_box_SECRETKEYBYTES: raise ValueError("Invalid secret key") k = lib.ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES) if lib.crypto_box_beforenm(k, pk, sk) != 0: raise CryptoError("An error occurred computing the shared key.") return lib.ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]
def crypto_box_afternm(message, nonce, k): """ Encrypts and returns the message ``message`` using the shared key ``k`` and the nonce ``nonce``. :param message: bytes :param nonce: bytes :param k: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise ValueError("Invalid nonce") if len(k) != crypto_box_BEFORENMBYTES: raise ValueError("Invalid shared key") padded = b"\x00" * crypto_box_ZEROBYTES + message ciphertext = lib.ffi.new("unsigned char[]", len(padded)) if lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k) != 0: raise CryptoError("An error occurred trying to encrypt the message") return lib.ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
def crypto_secretbox(key, message, nonce): """ Encrypts and returns the message ``message`` with the secret ``key`` and the nonce ``nonce``. :param key: bytes :param message: bytes :param nonce: bytes :rtype: bytes """ if len(key) != crypto_secretbox_KEYBYTES: raise ValueError("Invalid key") if len(nonce) != crypto_secretbox_NONCEBYTES: raise ValueError("Invalid nonce") padded = b"\x00" * crypto_secretbox_ZEROBYTES + message ciphertext = lib.ffi.new("unsigned char[]", len(padded)) if lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key) != 0: raise CryptoError("Encryption failed") ciphertext = lib.ffi.buffer(ciphertext, len(padded)) return ciphertext[crypto_secretbox_BOXZEROBYTES:]
def crypto_box_open_afternm(ciphertext, nonce, k): """ Decrypts and returns the encrypted message ``ciphertext``, using the shared key ``k`` and the nonce ``nonce``. :param ciphertext: bytes :param nonce: bytes :param k: bytes :rtype: bytes """ if len(nonce) != crypto_box_NONCEBYTES: raise ValueError("Invalid nonce") if len(k) != crypto_box_BEFORENMBYTES: raise ValueError("Invalid shared key") padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext plaintext = ffi.new("unsigned char[]", len(padded)) if lib.crypto_box_open_afternm( plaintext, padded, len(padded), nonce, k) != 0: raise CryptoError("An error occurred trying to decrypt the message") return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
def parse_private_key(stream, callback): ciphername = decode_string(stream) kdfname = decode_string(stream) kdfoptions = decode_string(stream) LOG.info("Ciphername: %s", ciphername) LOG.info("KDF: %s", kdfname) # LOG.debug("KDF options %s", kdfoptions) if kdfname not in (b"none", b"bcrypt"): raise ValueError("Invalid SSH Key format") if kdfname != b"none" and ciphername == b"none": raise ValueError("Invalid SSH Key format") if kdfname != b'none': # assert (kdfname == b"bcrypt") assert kdfoptions kdfoptions = io.BytesIO(kdfoptions) salt = decode_string(kdfoptions) rounds = int.from_bytes(kdfoptions.read(4), byteorder='big') LOG.info("Salt: %s", salt.hex()) LOG.info("Rounds: %d", rounds) assert not kdfoptions.read(), "There should be no trailing data in the kdfoptions buffer" else: LOG.debug("Not Encrypted") assert( not kdfoptions ) n = int.from_bytes(stream.read(4), byteorder='big') # u32 LOG.debug("Number of keys: %d", n) assert( n == 1 ) # Apparently always 1: https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L3857 decode_string(stream) # ignore the public keys private_ciphertext = decode_string(stream) # padded list of private keys assert not stream.read(), "There should be no trailing data" if ciphername == b'none': # LOG.debug('Non-Encrypted private data: %s', private_data) private_data = io.BytesIO(private_ciphertext) # no need to unpad return _get_skpk_from_private_blob(private_data) # ignore the comment # Encrypted content # LOG.debug('------ Encrypted private data (%d): %s', len(private_ciphertext), private_ciphertext) assert( callback and callable(callback) ) passphrase = callback().encode() # LOG.debug('Passphrase: %s', passphrase) if not passphrase and ciphername != b"none": raise ValueError("Passphrase required") dklen = get_derived_key_length(ciphername) # key length + IV length LOG.debug('Derived Key len: %d', dklen) derived_key = derive_key(kdfname, passphrase, salt, rounds, dklen=dklen) LOG.debug('Derived Key: %s', derived_key) decryptor = get_cipher(ciphername, derived_key).decryptor() assert( len(private_ciphertext) % _block_size(ciphername) == 0 ), "Invalid cipher block length" private_data = decryptor.update(private_ciphertext) + decryptor.finalize() # LOG.debug('------- Private data (%d): %s', len(private_data), private_data) if private_data[:4] != private_data[4:8]: # check don't pass LOG.debug('Check: %s != %s', private_data[:4], private_data[4:8]) raise CryptoError() private_data = io.BytesIO(private_data[8:]) # Note: we ignore the comment and padding after the priv blob return _get_skpk_from_private_blob(private_data) # no need to unpad