ENCRYPT, as its name suggests, provides multiple encryption algorithms for the calculator.
- Anthony Cagliano
- Adam Beckingham
- jacobly (modular exponentiation)
- Zeroko (information on sourcing randomness)
- CSRNG (Cryptographically-Secure Random Number Generator)
- Many random number generators, including the rand() implementation provided by
the toolchain are only statistically random, but not unpredictable. That suffices for many applications but not for cryptography. Otherwise-secure cryptography can be defeated if the primative that generates keys and salts is predictable. To that end, the developers of this library put significant effort into constructing a generator that satifies the constraints for cryptographic security to the best extent possible on the hardware. Those constraints are as follows:
- Statistically-random (passes all statistical randomness tests)
- Unpredictable (ref: next-bit test)
- Resistant to state-compromise; state compromise yields nothing of value
- Encryption
- A reversible transformation of data designed to render it difficult for an unauthorized party to read. A cipher is an algorithm that performs encryption and decryption.
- Symmetric Encryption
- An encryption system in which the same key works for both encryption and decryption. Symmetric encryption tends to be very fast and uses much smaller keys.
- Asymmetric Encryption
- An encryption system in which one key is used for encryption and a seperate key is used for decryption and there is some mathematical relationship between the two keys that allows them to reverse each other. Asymmetric encryption tends to be slow and uses much larger keys.
- Advanced Encryption Standard (AES)
- A fast symmetric encryption system that can encrypt arbitrary lengths of data in blocks of 128 bits (16 bytes). AES has three main variants, each of which take a key of different length:
- AES-128: Uses a 128-bit (16 byte) key, performs 10 rounds of encryption
- AES-192: Uses a 192-bit (24 byte) key, performs 12 rounds of encryption
- AES-256: Uses a 256-bit (32 byte) key, performs 14 rounds of encryption Rounds means number of times the algorithm repeats transformations on the data.
- RSA
-
RSA is a form of public key cryptography. In this construction, both parties need a public key and a private key. Typically the public key is used to encrypt outbound messages and the private key is used to decrypt inbound messages. In a public key system anyone can encrypt messages for a specific host since the public key is sent in the clear (hence the term "public"). However, only the intended recipient can decrypt those messages as the private key is not shared. This is possible because the public and private keys are inverses of each other such that:
// RSA encryption and decryption
encrypted = message ^ public_exponent % public modulus
message = encrypted ^ private_exponent % private modulus
The cryptographic strength of RSA comes from the difficulty of factoring huge prime numbers. In recent times better hardware and faster algorithms have made solving this problem easier. 1024-bit RSA has been broken for some time and most cryptographers suspect 2048-bit RSA only has a few years of viability left.
Asymmetric encryption is very slow. Using even RSA-1024 on the TI-84+ CE will take several seconds. For this reason, you usually do not use RSA for sustained encrypted communication. Use RSA to share a symmetric key, and then use AES for future messages. - Elliptic Curve Diffie-Hellman (ECDH)
- Diffie-Hellman is a key negotiation protocol. It is another form of public key cryptography in which two parties agree on a shared secret to use for symmetric encryption by exchanging a public key that is the product of a private key and some scalar. Standard Diffie-Hellman uses the same general schema as RSA involving primes (p) and a primitive root modulo p (G). The properties of these keys are such that the following is true:
// The Diffie-Hellman algorithm
G ^ Pa % p = Ua // P = private, U = public, a = alice
G ^ Pb % p = Ub // P = private, U = public, b = bob
// Alice and Bob exchange public keys (Ua <==> Ub)
Ub ^ Pa % p = secret
Ua ^ Pb % p = secret
// Note that both parties end up with a common shared secret.
For the same reasons as with RSA this requires extremely large values for p and G and it also suffers from the same weaknesses that render it increasingly insecure as hardware and algorithms get better at factoring the values. Enter elliptic curve cryptography. In the case of elliptic curve Diffie-Hellman, G is a base point of maximal order on the elliptic curve and the public key is the result of multiplying that point by the private key over a finite field. The revised algorithm can be expressed like so:
// The Elliptic Curve Diffie-Hellman algorithm
Pa * G = Ua // P = private, U = public, a = alice
Pb * G = Ub // P = private, U = public, b = bob
// Alice and Bob exchange public keys (Ua <==> Ub)
Ub * Pa = secret
Ua * Pb = secret
// Note that both parties end up with a common shared secret.
The behavior of an elliptic curve over a finite field lends itself to the creation of keys that are more complicated to crack and it so follows that the necessary key lengths are much smaller. The curve implemented by this library, SECT233k1, has a degree of 233 which also defines the maximum length of the private key. Just 233 bits for elliptic curve Diffie-Hellman. Versus several thousand for standard Diffie-Hellman and RSA. Quite the difference.
Defines the byte length of an AES-128 key.
Defines the byte length of an AES-192 key.
Defines the byte length of an AES-256 key.
Defines the block size of the AES cipher.
Defines the length of the AES initialization vector.
((((plaintext_len)%CRYPTX_AES_BLOCK_SIZE)==0) ? \
(len) + CRYPTX_AES_BLOCK_SIZE : (((len)>>4) + 1)<<4)
Defines a macro to return the necessary length for a padded AES plaintext.
((padding_mode)<<2) | AES_MODE_CBC
Defines a macro to enable AES CBC cipher mode and pass relevant configuration options.
See cryptx_aes_padding_modes.
((0x0f & (counter_len))<<8) | ((0x0f & (nonce_len))<<4) | AES_MODE_CTR
Defines a macro to enable AES CTR cipher mode and pass relevant configuration options.
Pass 0 for nonce_len and counter_len to set default options.
Defines the maximum byte length of an RSA public modulus supported by this library.
Defines the byte length of an ECDH private key supported by this library.
Defines the byte length of an ECDH public key supported by this library.
SAMPLING_THOROUGH = 0,
SAMPLING_FAST = 1
} cryptx_csrng_sampling_mode;
Defines sampling modes for cryptx_csrand_init().
SAMPLING_THOROUGH: 1024 tests per bit
SAMPLING_FAST: 512 tests per bit
AES_MODE_CBC,
AES_MODE_CTR
};
Defines supported AES cipher modes.
PAD_PKCS7,
PAD_DEFAULT = PAD_PKCS7,
PAD_ISO2
;};
Defines supported padding schemes for AES CBC mode
AES_OK,
AES_INVALID_ARG,
AES_INVALID_MSG,
AES_INVALID_CIPHERMODE,
AES_INVALID_PADDINGMODE,
AES_INVALID_CIPHERTEXT,
AES_INVALID_OPERATION
} aes_error_t;
Defines possible responses codes from calls to the AES API.
RSA_OK,
RSA_INVALID_ARG,
RSA_INVALID_MSG,
RSA_INVALID_MODULUS,
RSA_ENCODING_ERROR
} rsa_error_t;
Defines possible response codes from calls to the RSA API
ECDH_OK,
ECDH_INVALID_ARG,
ECDH_PRIVKEY_INVALID,
ECDH_RPUBKEY_INVALID
} ecdh_error_t;
Defines possible response codes from calls to the ECDH API
Defines state data for an AES context.
Defines state data for an ECDH context.
Initializes the (HW)RNG.
mode: A flag specifying the sampling mode. See cryptx_csrng_sampling_modes.
output: true on success, false on failure.
SAMPLING_THOROUGH ensures a more entropic source, but takes longer (~4s).
SAMPLING_FAST takes less time (~2s) but may not select the most entropic bit.
Returns a securely random 32-bit (4 byte) integer.
output: A 32-bit random integer
Fills a buffer with securely random bytes.
buffer: Pointer to a buffer to fill with random bytes.
size: Length of the buffer.
output: true on success, false on failure.
struct cryptx_aes_ctx* context,
const void* key, size_t keylen,
const void* iv, uint24_t flags);
Initializes a stateful and one-directional AES context.
context: Pointer to an AES context.
key: Pointer to the key to use with the AES context
keylen: Length of the key, in bytes
iv: Pointer to a 16-byte initialization vector (salt)
flags: A series of cipher options bitwise-ORd together.
See CRYPTX_AES_CBC_FLAGS() and CRYPTX_AES_CTR_FLAGS()
output: An aes_error_t indicating the status of the AES operation.
struct cryptx_aes_ctx* context,
const void* plaintext, size_t len,
void* ciphertext);
Encrypts a stream of data and updates the AES context such that (1) it will return AES_INVALID_OPERATION if used with decryption, and (2) you can pass the next stream of data to aes_encrypt() using the same context.
context: Pointer to an initialized AES context.
plaintext: Pointer to stream of data to encrypt.
len: Length of data to encrypt.
ciphertext: Pointer to buffer to write encrypted data to.
output: An aes_error_t indicating the status of the AES operation.
struct cryptx_aes_ctx* context,
const void* ciphertext, size_t len,
void* plaintext);
Decrypts a stream of data and updates the AES context such that (1) it will return AES_INVALID_OPERATION if used with encryption, and (2) you can pass the next stream of data to aes_decrypt() using the same context.
context: Pointer to an initalized AES context.
plaintext: Pointer to stream of data to decrypt.
len: Length of data to decrypt.
ciphertext: Pointer to buffer to write decrypted data to.
output: An aes_error_t indicating the status of the AES operation.
const void* msg, size_t msglen,
const void* pubkey, size_t keylen,
void* ciphertext, uint8_t oaep_hash_alg);
Encrypts a message using the RSA algorithm, applying the Optimal Asymmetic Encryption Padding scheme (OAEPv2.2) prior to encryption.
msg: Pointer to message to encrypt.
msglen: Length of message to encrypt.
pubkey: Pointer to public modulus to encrypt with.
keylen: Length, in bytes, of the public modulus.
ciphertext: Pointer to buffer to write encrypted message to.
oaep_hash_algs: The numeric ID of the hashing algorithm to use within OAEP encoding.
See cryptx_hash_algorithms.
output: An rsa_error_t indicating the status of the RSA operation
Fills the private key with random bytes and generates a public key given base point G and the randomized private key.
context: Pointer to an ECDH context.
output: An ecdh_error_t indicating the status of the ECDH operation.
The context is updated with the public key, accessible at context.pubkey.
The private key is also accessible at context.privkey.
Do not edit the context manually after calling this function, you will corrupt the state. Only access these structure members directly to read out the public key for sending to the remote host.
struct cryptx_ecdh_ctx* context,
const uint8_t* rpubkey, uint8_t* secret);
Generates a shared secret given an ECDH context and a remote public key.
Uses the cofactor variant of ECDH. SECT233k1 has a cofactor of 4.
context: Pointer to an initialized ECDH context.
rpubkey: Pointer to a remote public key.
secret: Pointer to buffer to write the secret to.
output: An ecdh_error_t indicating the status of the ECDH operation.
Do not edit the context manually, you will corrupt the state.
Do not use the generated secret directly for symmetric encryption. Pass it to a KDF or cryptographic hash to generate a digest and use that as your encryption key.
Authenticated encryption is an encryption scheme that produces a ciphertext that is not only obfuscated but also has its integrity and authenticity verifiable. This can be accomplished in a few ways, the most common of which are: (1) appending a signature, hash, or keyed hash to a message, and (2) implementing a cipher mode that integrates authentication.
#2 above is not implemented in CryptX. Most of the authenticating cipher modes are computationally-intensive without hardware acceleration and may not be feasible for use on the TI-84+ CE. While consid- eration is being given to potentially adding a cipher mode such as OCB or GCM if a sufficiently-optimized implementation for this platform can be found (or devised), it is possible to construct a ciphertext guarded against tampering by using method #1, which this library does provide for.
It is recommended that whenever you are sending data you need to be truly secure with this library, you always embed a keyed hash into the message that the recipient can validate. This functionality is provided by the HMAC implementation shown earlier in this document. Proper application of HMAC for ciphertext integrity requires the following considerations:
- Initialization vector/nonce blocks for encryption are securely pseudo-random.
- Encryption and HMAC keys are also securely pseudo-random and are long enough to be considered secure. Minimum key sizes recommended are 16 bytes.
- You are not using your encryption key as your HMAC key or vice versa. There are attack vectors that result from using the same key for encryption and authentication.
- Append a keyed hash (HMAC) of the initialization vector/nonce, encrypted message, and any other associated data such as packet headers to the outgoing message. On the receiving side, validate the HMAC before decryption and reject any message that does not authenticate. The HMAC key can be an application secret known to both parties or a generated nonce shared alongside the AES encryption key using RSA or another public key encryption method.
#include <hashlib.h>
#include <encrypt.h>
// this assumes that the AES secret ‘aes_key‘ and the HMAC secret ‘hmac_key‘
// have been negotiated beforehand.
// let’s send a simple ascii string
char* msg = "The daring fox jumped over the moon."
// the header is a size word, containing size of string plus our IV
// header can really be whatever you want, but some arbitrary nonsense as an example
size_t header = sizeof(msg)+AES_IVSIZE;
// allocate the memory we need
cryptx_aes_ctx ctx;
cryptx_hmac_ctx hmac;
uint8_t iv[AES_IVSIZE];
// doing this first allows us to
hmac_init(&hmac, hmac_key, sizeof hmac_key, SHA256);
// allocate a digest of the size we need
uint8_t hmac_digest[hmac.digest_len];
// !!!! NEVER PROCEED IF csrand_init() FAILS !!!
if(!cryptx_csrand_init()) return false;
cryptx_csrand_fill(iv, AES_IVSIZE);
// initialize AES context with mode , key , and iv
cryptx_aes_init(&ctx, aes_key, sizeof aes_key, iv, CRYPTX_AES_CTR_FLAGS(8, 8));
// encrypt message
// aes_encrypt supports in-place encryption
cryptx_aes_encrypt(&ctx, msg, strlen(msg), msg);
// hash everything you are sending, except the hash itself
cryptx_hmac_update(&hmac, &header, sizeof header);
cryptx_hmac_update(&hmac, iv, sizeof iv);
cryptx_hmac_update(&hmac, msg, strlen(msg));
cryptx_hmac_final(&hmac, hmac_digest);
// ps_queue/send are psuedo functions implying queueing data to send
// and then sending it
ps_queue(&header, sizeof header);
ps_queue(iv, sizeof iv);
ps_queue(msg, sizeof msg);
ps_queue(hmac_digest, sizeof hmac_digest);
ps_send();