Utility.Crypto (Portico/Z v0.1.0)
Wrappers + sane defaults around common cryptographic functions.
We are NOT writing our own crypto, that is a bad idea.
You may wish to configure a default public key for use in encryption. In config.exs
:
config :utility, Utility.Crypto,
public_key: "-----BEGIN PUBLIC...."
We don't do this in production, but in development and test environments you can also configure a default private key for decoding:
config :utility, Utility.Crypto,
private_key: "-----BEGIN RSA PRIVATE...."
Link to this section Summary
Functions
Decode an RSA key for use with encryption/decryption
Decrypt a value encoded with Utility.Crypto.encrypt/2
Decrypt a value encoded with Utility.Crypto.encrypt/2
Encrypt an arbitrary value using the system default public key
Encrypt an arbitrary value to a Base64-encoded string
Generate a hash from given string data
.
Generate an HMAC for string data
with a given string key
.
Generate a variable-length hash for any data
from a given alphabet.
Generate a random string. Sometimes you need those!
Link to this section Functions
decode_key(text)
Decode an RSA key for use with encryption/decryption
The key
can be any RSA public key in PEM format. You can tell whether it's RSA because it'll start with -----BEGIN PUBLIC KEY-----
or -----BEGIN RSA PRIVATE KEY-----
.
Here's how you generate a private / public keypair for that kind of thing:
# in your terminal
ssh-keygen -t rsa -b 4096 -C "youremail@explo.org" -m PEM -f your_password_file
Now your_password_file is your private key and your_password_file.pub is your public key, but it's not in RSA format yet! Here's how you do that:
# encode the SSH-generated public key into PEM format
openssl rsa -in your_password_file -pubout -out your_password_file.pub.pem
Now you can use your_password_file.pub.pem to encrypt:
{:ok, public_key_text} = File.read("your_password_file.pub.pem")
public_key = Crypto.decode_key(public_key_text)
See Utility.Crypto.encrypt/2
and Utility.Crypto.decrypt/2
for more details about using keys in encryption and decryption.
decrypt(message)
Decrypt a value encoded with Utility.Crypto.encrypt/2
With no key passed, this will default to the key configured in the application environment. This whole function exists mostly for testing and doesn't get used in production at all (because we don't store private keys for message decryption on our production server).
See Utility.Crypto.decrypt/2
for more details.
Examples
"secret message" |> Utility.Crypto.encrypt() |> Utility.Crypto.decrypt()
"secret message"
decrypt(cipher, private_key)
Specs
Decrypt a value encoded with Utility.Crypto.encrypt/2
The private_key
must be the output of Utility.Crypto.decode_key/1
.
Examples
Utility.Crypto.decrypt(ciphertext, private_key)
"your secret message"
encrypt(message)
Encrypt an arbitrary value using the system default public key
See Utility.Crypto.encrypt/2
for more details.
Examples
Utility.Crypto.encrypt("secret message")
"hEh1pki3v83GdjVrWj15SyRSln0cMAu+...and so forth"
encrypt(message, public_key)
Specs
Encrypt an arbitrary value to a Base64-encoded string
Examples
Utility.Crypto.encrypt("your secret message", public_key)
"MR0aLJrNNuQBqLPQgYtRNoA4WVBtj2B0aVwdH8Y6W+jU67JCD"
hash(data)
Specs
Generate a hash from given string data
.
The hash will always be the same if the data is the same. This is the nice thing about hashes :)
The return value will be a base-16 (hex) string.
Examples
iex> Crypto.hash("lincoln logs")
"2ED04E8C7946D3B1F479961CF5D05CBE67762064001F7CDF86B7C7DF9B7D2433"
iex> Crypto.hash("gumby")
"72B21B00EED509BC401DE942A291B4A59EEFAB87345EB6751923C1A1643B28C9"
hmac(key, data)
Specs
Generate an HMAC for string data
with a given string key
.
An HMAC is similar to a hash, but has a value mixed in to obfuscate retrieval or make it consistent among a data set. You would typically use HMAC for signatures or encrypting data. See https://latacora.singles/2018/04/03/cryptographic-right-answers.html for more details about hashes and why we're using the hashing algorithms we're using.
Examples
iex> Crypto.hmac("matchbox", "car")
"6352F4426D3344EBFF265C67A442AC09BE321AD85A010807456F16AB275BE02F"
iex> Crypto.hmac("railroad", "car")
"B393F0EFDBE81C2D295BE1EE47BE3BD38F2E1158F21884A00087C0FB6DD7C879"
letter_hash(data, args \\ [])
Specs
Generate a variable-length hash for any data
from a given alphabet.
Similar to hash/1
, except the returned value is shortened and converted to
an optional given alphabet (set of characters). data
is typically a
database ID that we want to hash, but could be any value.
Clearly, decreasing the length of a hash increases the chances of a hash
collision (same hash returned for two different pieces of data), but you can
compensate for that by increasing the available alphabet. For example, given
@letter_hash_default_alphabet
, which is 32 characters long, and a
@letter_hash_default_length
of 7, we have 32x32x32x32x32x32x32 possible
variations, or 1,099,511,627,776 variations, which is OK.
Arguments
There are some optional arguments you can pass:
:alphabet
- string - A string containing the acceptable letters to use in the returned hash:length
- integer - The length of the returned hash; minimum is 1, maximum is 64:signature
- string - An optional "salt" to help identify/obfuscate your hashes; seehmac/2
for details.
Examples
iex> Crypto.letter_hash("marbles")
"62TH7HS"
iex> Crypto.letter_hash("marbles", signature: "corvette")
"SER9K7W"
iex> Crypto.letter_hash("jack-in-the-box", length: 4)
"ATHF"
iex> Crypto.letter_hash("pickup sticks", length: 6, alphabet: "ᚠᚦᚹᛞ")
"ᚹᚠᛞᚹᚠᚹ"
iex> Crypto.letter_hash("pickup sticks", alphabet: "🍱🗿🦅🤖")
"🍱🍱🍱🤖🤖🗿🦅"
random_string(length \\ 64)
Specs
Generate a random string. Sometimes you need those!
Examples
iex> Crypto.random_string(6) |> String.length()
6