+' Permission to use, copy, modify, and/or distribute this software for any
+' purpose with or without fee is hereby granted, provided that the above
+' copyright notice and this permission notice appear in all copies.
+'
+' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+'
+SuperStrict
+
+Rem
+bbdoc: Cryptographic Utils
+End Rem
+Module Crypto.Crypto
+
+ModuleInfo "Version: 1.00"
+ModuleInfo "Author: Bruce A Henderson."
+ModuleInfo "License: ISC"
+
+ModuleInfo "History: 1.00"
+ModuleInfo "History: Initial Release."
+
+Import "common.bmx"
+
+
+hydro_init()
+
+Rem
+bbdoc: Generate unpredictable data, suitable for creating secret keys.
+about:
+> If this is used in an application inside a VM, and the VM is snapshotted and restored, then the above functions will produce the same output.
+End Rem
+Type TCryptoRandom
+
+ Rem
+ bbdoc: Returns an unpredictable value between 0 and $ffffffff (inclusive).
+ End Rem
+ Function Random:UInt()
+ Return hydro_random_u32()
+ End Function
+
+ Rem
+ bbdoc: Returns an unpredictable value between 0 and @upperBound (excluded).
+ about: Unlike `Random() Mod upperBound`, it does its best to guarantee a uniform distribution of the possible
+ output values even when @upperBound is not a power of 2.
+ End Rem
+ Function Uniform:UInt(upperBound:UInt)
+ Return hydro_random_uniform(upperBound)
+ End Function
+
+ Rem
+ bbdoc: Fills @size bytes starting at @buf with an unpredictable sequence of bytes, derived from a secret seed.
+ End Rem
+ Function FillBuffer(buf:Byte Ptr, size:Size_T)
+ hydro_random_buf(buf, size)
+ End Function
+
+ Rem
+ bbdoc: Fills an array of bytes with an unpredictable sequence of bytes, derived from a secret seed.
+ End Rem
+ Function FillBuffer(buf:Byte[])
+ hydro_random_buf(buf, Size_T(buf.length))
+ End Function
+
+ Rem
+ bbdoc: Fills a key with an unpredictable sequence of @bytes in length, derived from a secret seed.
+ End Rem
+ Function FillKey(key:TCryptoKey Var, bytes:Size_T = 32)
+ If key = Null Then
+ key = New TCryptoKey
+ End If
+
+ If Not key.key Or key.key.length <> bytes Then
+ key.key = New Byte[bytes]
+ End If
+
+ FillBuffer(key.key)
+ End Function
+
+ Rem
+ bbdoc: Erases part of the state and replaces the secret key, making it impossible to recover the previous states in case the current one ever gets exposed due to a vulnerability.
+ End Rem
+ Function Ratchet()
+ hydro_random_ratchet()
+ End Function
+
+ Rem
+ bbdoc: Reseeds the random number generator.
+ about: Must be called after a `fork()` call.
+ End Rem
+ Function Reseed()
+ hydro_random_reseed()
+ End Function
+
+End Type
+
+Rem
+bbdoc: A base for key implementations.
+End Rem
+Type TCryptoKey
+
+ Field key:Byte[]
+
+ Rem
+ bbdoc: Returns a String representation of the key.
+ End Rem
+ Method ToString:String()
+ Return TBase64.Encode(key)
+ End Method
+
+ Rem
+ bbdoc: Retrieves a key from its String representation.
+ End Rem
+ Function FromString:TCryptoKey(k:String)
+ Local key:TCryptoKey = New TCryptoKey
+ key.key = TBase64.Decode(k)
+ Return key
+ End Function
+
+End Type
+
+Rem
+bbdoc: A secret key suitable for use with the #TCryptoHash functions.
+End Rem
+Type TCryptoHashKey Extends TCryptoKey
+
+ Method New()
+ key = New Byte[CRYPTO_HASH_KEYBYTES]
+ End Method
+
+ Rem
+ bbdoc: Retrieves a key from its String representation.
+ End Rem
+ Function FromString:TCryptoHashKey(key:String)
+ Local hashKey:TCryptoHashKey = New TCryptoHashKey
+ hashKey.key = TBase64.Decode(key)
+
+ If hashKey.key.length <> CRYPTO_HASH_KEYBYTES Then
+ If cLen <> (mLen + CRYPTO_SECRETBOX_HEADERBYTES) Then
+ Throw "'c' must be " + mLen + " + " + CRYPTO_SECRETBOX_HEADERBYTES + " bytes"
+ End If
+ Return bmx_hydro_secretbox_encrypt(c, m, mLen, msgId, context, key.key)
+ End Function
+
+ Rem
+ Rem
+ bbdoc: Encrypts a message @m using a @context, a secret @key and a message counter @msgId.
+ about: It puts the ciphertext whose length is @CRYPTO_SECRETBOX_HEADERBYTES + m.length into c.
+
+ The header includes an automatically-generated 160-bit nonce as well as a 128-bit authentication tag.
+
+ A nonce doesn't have to be provided: it is automatically computed using the output of the PRNG and a keyed hash of the message
+ and its metadata. This prevents catastrophic failures even if the PRNG cannot be trusted.
+
+ @msgId is an optional message tag. For example, if 3 messages are sent to the same recipient using the same key, these messages
+ can sequentially use 0, 1 and 2 as identifiers.
+
+ If the recipient expects message 2, but receives a message with a different identifier, it will not decrypt it even if it was
+ encrypted with the correct key.
+
+ This can be used to discard duplicate or old messages.
+
+ A @msgId doesn't have to be secret and it doesn't have to be sequential either. Some applications might prefer a coarse timestamp instead.
+ Any value up to 2^64-1 is acceptable.
+
+ If this mechanism is not required by an application, using a constant @msgId such as 0 is also totally fine. Message identifiers are
+ optional and do not have to be unique.
+ End Rem
+ Function Encrypt:Int(c:Byte[], m:Byte[], msgId:ULong, context:String, key:TCryptoKey)
+ If c.length <> m.length + CRYPTO_SECRETBOX_HEADERBYTES Then
+ Throw "'c' must be " + m.length + " + " + CRYPTO_SECRETBOX_HEADERBYTES + " bytes"
+ End If
+ Return bmx_hydro_secretbox_encrypt(c, m, Size_T(m.length), msgId, context, key.key)
+ End Function
+
+ Rem
+ bbdoc: Decrypts the ciphertext @c of length @cLen (which includes the @CRYPTO_SECRETBOX_HEADERBYTES bytes header) using the secret key @key, the @context and the message identifier @msgId.
+ about: If the authentication tag can be verified using these parameters, the function stores the decrypted message into @m.
+ The length of this decrypted message is cLen - CRYPTO_SECRETBOX_KEYBYTES. It then returns #True.
+
+ If the authentication tag doesn't appear to be valid for these parameters, the function returns #False.
+ bbdoc: Decrypts the ciphertext @c (which includes the #CRYPTO_SECRETBOX_HEADERBYTES bytes header) using the secret key @key, the @context and the message identifier @msgId.
+ about: If the authentication tag can be verified using these parameters, the function stores the decrypted message into @m.
+ The length of this decrypted message is c.length - CRYPTO_SECRETBOX_KEYBYTES. It then returns #True.
+
+ If the authentication tag doesn't appear to be valid for these parameters, the function returns #False.
+ End Rem
+ Function Decrypt:Int(m:Byte[], c:Byte[], msgId:ULong, context:String, key:TCryptoKey)
+ If m.length <> (c.length - CRYPTO_SECRETBOX_HEADERBYTES) Then
+ Throw "'m' must be " + c.length + " - " + CRYPTO_SECRETBOX_HEADERBYTES + " bytes"
+ bbdoc: Verifies that a received probe @probe is valid for the ciphertext @c whose length is @cLen, using the @context and the shared secret key @key that was initially used to compute the probe.
+ about: It returns #True on success, and #False if the probe didn't pass verification.
+ End Rem
+ Function ProbeVerify:Int(probe:Byte Ptr, probeLen:Size_T, c:Byte Ptr, cLen:Size_T, context:String, key:TCryptoKey)
+ If probeLen <> CRYPTO_SECRETBOX_PROBEBYTES Then
+ Throw "'probe' must be " + CRYPTO_SECRETBOX_PROBEBYTES + " bytes"
+ bbdoc: Verifies that a received probe @probe is valid for the ciphertext @c, using the @context and the shared secret key @key that was initially used to compute the probe.
+ about: It returns #True on success, and #False if the probe didn't pass verification.
+ End Rem
+ Function ProbeVerify:Int(probe:Byte[], c:Byte[], context:String, key:TCryptoKey)
+ If probe.length <> CRYPTO_SECRETBOX_PROBEBYTES Then
+ Throw "'probe' must be " + CRYPTO_SECRETBOX_PROBEBYTES + " bytes"
+ bbdoc: Verifies the signature into @csig using the public key from @kp.
+ about: Returns #False if the signature doesn't appear to be valid for the given message, context and public key, or #True if it could be successfully verified.
+about: Using key exchange, two parties can securely compute a set of ephemeral, shared secret keys, that can be used to securely exchange messages.
+
+The general pattern two build a secure channel is:
+
+* Pick the variant that fits your application needs
+* Use the functions from that variant to build and parse packets to be exchanged between parties
+* Eventually, both parties will compute a shared secret, that can be used with #TCryptoSecretbox.
+End Rem
+Type TCryptoKeyExchange
+
+ Rem
+ bbdoc: Generates a long-term key pair.
+ about: These long-term keys can be reused indefinitely, even though rotating them from time to time is highly recommended
+ in case the secret key ever gets leaked.
+ End Rem
+ Function KeyGen:TCryptoExchangeKeyPair()
+ Local kp:TCryptoExchangeKeyPair = New TCryptoExchangeKeyPair
+ bmx_hydro_kx_keygen(kp.secretKey, kp.publicKey)
+ Return kp
+ End Function
+
+ Rem
+ bbdoc: Computes a session key pair @sessionKeyPair using the server's public key @publicKey, and builds a packet @packet1 that has to be sent to the server.
+ returns: #True on success, and #False on error.
+ about: This variant is designed to anonymously send messages to a recipient using its public key.
+ * What the client needs to know about the server: <b>the server's public key</b>
+ * What the server needs to know about the client: <b>nothing</b>
+ End Rem
+ Function N1:Int(sessionKeyPair:TCryptoSessionKeyPair Var, packet:TCryptoNPacket Var, preSharedKey:TCryptoKey, serverKeyPair:TCryptoExchangeKeyPair)
+ bbdoc: Validates the request, computes an ephemeral key pair, puts it into @sessionKeyPair, and stores the packet to send to the client into @packet2.
+ returns: #True on success, and #False on error.
+ End Rem
+ Function KK2:Int(sessionKeyPair:TCryptoSessionKeyPair Var, packet2:TCryptoKK2Packet Var, packet1:TCryptoKK1Packet, peerKeyPair:TCryptoExchangeKeyPair, keyPair:TCryptoExchangeKeyPair)
+ bbdoc: Initializes the local state @state, computes an ephemeral key pair, and puts the first packet to send to the server into @packet1.
+ returns: #True on success, and #False on error.
+ about: Using this API, a client and a server can securely generate and exchange session keys.
+
+ This API is designed for online protocols, and requires two round trips:
+
+ * The initiator (client) sends a key exchange request.
+ * The listener (server) receives the request, validates it, and sends a packet to the client.
+ * The client validates the packet, compute the session keys, and sends a last packet to the server. The client learns the server public key as well, and can drop the connection if it doesn't match an expected public key.
+ * The server use this packet and previous data in order to compute the same session keys. The server learns the client public key as well.
+
+ Two sessions keys are eventually computed. The former can be used to encrypt data sent from the client to the server, the later can be used
+ in the other direction.
+
+ If the the pre-shared secret @preSharedKey is set, the server can detect a suspicious request after the first packet is received.
+ Without a pre-shared secret, an additional round trip is required.
+ End Rem
+ Function XX1:Int(state:TCryptoExchangeState Var, packet1:TCryptoXX1Packet Var, preSharedKey:TCryptoKey)
+ bbdoc: Validates the request, computes an ephemeral key pair, and puts the packet to send to the client into @packet2.
+ returns: #True on success, and #False if the received packet doesn't appear to be valid.
+ End Rem
+ Function XX2:Int(state:TCryptoExchangeState Var, packet2:TCryptoXX2Packet Var, packet1:TCryptoXX1Packet, preSharedKey:TCryptoKey, keyPair:TCryptoExchangeKeyPair)
+ bbdoc: Validates the packet, computes a session key and puts it into @sessionKeyPair.
+ returns: #True on success, and #False if the received packet doesn't appear to be valid.
+ about: @sessionKeyPair contains the final session key at this point.
+ A payload can already be sent by the client without waiting for the server to compute the session keys on its end.
+ End Rem
+ Function XX3:Int(state:TCryptoExchangeState, sessionKeyPair:TCryptoSessionKeyPair Var, packet3:TCryptoXX3Packet Var, peerKeyPair:TCryptoExchangeKeyPair Var, packet2:TCryptoXX2Packet, preSharedKey:TCryptoKey, keyPair:TCryptoExchangeKeyPair)
+ bbdoc: Validates the packet, computes the session key (identical to the one computed by the client) and puts it into @sessionKeyPair.
+ returns: #True on success, and #False if the received packet doesn't appear to be valid.
+ End Rem
+ Function XX4:Int(state:TCryptoExchangeState, sessionKeyPair:TCryptoSessionKeyPair Var, peerKeyPair:TCryptoExchangeKeyPair Var, packet3:TCryptoXX3Packet, preSharedKey:TCryptoKey)
+about: Secret keys used to encrypt or sign confidential data have to be chosen from a very large keyspace.
+
+However, passwords are usually short, human-generated strings, making dictionary attacks practical.
+
+Password hashing functions derive a high-entropy secret key of any size from a password.
+
+The generated key will have the size defined by the application, no matter what the password length is.
+* The same password hashed with same parameters will always produce the same output.
+* The function deriving a key from a password is CPU intensive, to mitigate brute-force attacks by requiring a significant effort to verify each password.
+
+Common use cases:
+* Password storage, or rather: storing what it takes to verify a password without having to store the actual password.
+* Deriving a secret key from a password, for example for disk encryption
+End Rem
+Type TCryptoPasswordHash
+
+ Rem
+ bbdoc: Generates a key used to encrypt all hashed passwords, along with their parameters.
+ about: Hashed passwords and master keys should be stored in different places: hashed passwords are typically
+ stored in a database, whereas the master key can be statically loaded or hardcoded in the application.
+
+ If the database ever gets breached, the list of hashed passwords will be completely useless without the master password.
+
+ The storage format supports reencryption and algorithm upgrades.
+ End Rem
+ Function KeyGen:TCryptoPWHashMasterKey()
+ Local key:TCryptoPWHashMasterKey = New TCryptoPWHashMasterKey
+ bmx_hydro_pwhash_keygen(key.key)
+ Return key
+ End Function
+
+ Rem
+ bbdoc: Derives a deterministic high-entropy key of any length (@hLen bytes) from a @password, a @context, a master key @masterKey and a set of parameters for the hash function.
+ about: The resulting key is put into @h.
+* @opslimit is the number of iterations. The higher the number, the slower the function will be, and the more secure the end result will be against brute-force attacks. This should be adjusted according to the hardware, and to application constraints.
+* @memlimit is the maximum amount of memory to use. The current function use a fixed amount of memory, and ignores this parameter. It can be unconditionally set to 0.
+* @threads is the number of threads. The current function ignores this parameter. It can be unconditionally set to 1.
+
+ This function can be used to derive a key from a password if no other information has been stored. For example, it can be used to
+ encrypt/decrypt a file using nothing but a password.
+ bbdoc: Derives a deterministic high-entropy key of any length from a @password, a @context, a master key @masterKey and a set of parameters for the hash function.
+ about: The resulting key is put into @h.
+* @opslimit is the number of iterations. The higher the number, the slower the function will be, and the more secure the end result will be against brute-force attacks. This should be adjusted according to the hardware, and to application constraints.
+* @memlimit is the maximum amount of memory to use. The current function use a fixed amount of memory, and ignores this parameter. It can be unconditionally set to 0.
+* @threads is the number of threads. The current function ignores this parameter. It can be unconditionally set to 1.
+
+ This function can be used to derive a key from a password if no other information has been stored. For example, it can be used to
+ encrypt/decrypt a file using nothing but a password.
+ bbdoc: Computes a fixed-length (#CRYPTO_PWHASH_STOREDBYTES bytes), hashed, encrypted, authenticated representative of the @password, that can be safely stored in a database.
+ about: This representative can be used to later check if a user provided password is likely to be the original one,
+ without ever storing the password in the database.
+
+ The function encrypts and authenticates the representative and the parameters using the master key @masterKey. All passwords can safely
+ be encrypted using the same, long-term master key. Applications can also choose to derive @masterKey from a master-master key, and a
+ unique user identifier.
+
+ The representative includes @opsLimit, @memLimit and @threads: these do not have to be stored separately.
+
+ Note that the representative is not a string: this is binary data, that must be stored as a blob in a database, or encoded
+ as a string (for example as a hex value or using a safe base64 variant).
+ End Rem
+ Function Create:Int(stored:TCryptoPWHashStoredKey Var, password:String, masterKey:TCryptoPWHashMasterKey, opsLimit:ULong, memLimit:Size_T, threads:Int = 1)
+ bbdoc: Reencrypts a representative stored using the current master key @masterKey and a new master key @newMasterKey.
+ about: It updates stored in-place and returns #True on success. If the representative couldn't be decrypted using @masterKey, the function returns #False.
+ End Rem
+ Function Reencrypt:Int(stored:TCryptoPWHashStoredKey, masterKey:TCryptoPWHashMasterKey, newMasterKey:TCryptoPWHashMasterKey)
+ bbdoc: Upgrades in-place a previously computed representative stored encrypted using the master key @masterKey, to the new parameters @opslimit, @memlimit and @threads.
+ returns: #True on success, or #False if the data couldn't be decrypted using the provided master password.
+ about: If previously passwords become too fast to verify after a hardware upgrade, stored representatives can be upgraded with new
+ parameters without requiring the original passwords.
+
+ Note that parameters can only be increased. Trying to reduce the value of an existing parameter will not change the original value.
+ End Rem
+ Function Upgrade:Int(stored:TCryptoPWHashStoredKey, masterKey:TCryptoPWHashMasterKey, opsLimit:ULong, memLimit:Size_T, threads:Int = 1)
+Local message:String = "Just a castaway, an island lost at sea. Another lonely day with no one here but me. More loneliness than any man could bear. Rescue me before I fall into despair."
+The Hydrogen library is a small, easy-to-use, hard-to-misuse cryptographic library.
+
+Features:
+- Consistent high-level API, inspired by libsodium. Instead of low-level primitives, it exposes simple functions to solve common problems that cryptography can solve.
+- 100% built using just two cryptographic building blocks: the [Curve25519](https://cr.yp.to/ecdh.html) elliptic curve, and the [Gimli](https://gimli.cr.yp.to/) permutation.
+- Small and easy to audit. Implemented as one tiny file for every set of operation, and adding a single `.c` file to your project is all it takes to use libhydrogen in your project.
+- The whole code is released under a single, very liberal license (ISC).
+- Zero dynamic memory allocations and low stack requirements (median: 32 bytes, max: 128 bytes). This makes it usable in constrained environments such as microcontrollers.
+- Portable: written in standard C99. Supports Linux, *BSD, MacOS, Windows, and the Arduino IDE out of the box.
+- Can generate cryptographically-secure random numbers, even on Arduino boards.
+- Attempts to mitigate the implications of accidental misuse, even on systems with an unreliable PRG and/or no clock.
+
+Non-goals:
+- Having multiple primitives serving the same purpose, even to provide compatibility with other libraries.
+- Networking -- but a simple key exchange API based on the Noise protocol is available, and a STROBE-based transport API will be implemented.
+- Interoperability with other libraries.
+- Replacing libsodium. Libhydrogen tries to keep the number of APIs and the code size down to a minimum.
+The documentation is maintained in the [libhydrogen wiki](https://github.com/jedisct1/libhydrogen/wiki).
+
+The legacy libhydrogen code (leveraging XChaCha20, SipHashX, BLAKE2SX, Curve25519) remains available in the [v0 branch](https://github.com/jedisct1/libhydrogen/tree/v0).
+paragraph=Consistent high-level API, inspired by libsodium. Instead of low-level primitives, it exposes simple functions to solve common problems that cryptography can solve.