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

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

Link to this function

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.

Link to this function

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"
Link to this function

decrypt(cipher, private_key)

Specs

decrypt(cipher :: String.t(), private_key :: term()) :: String.t()

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"
Link to this function

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"
Link to this function

encrypt(message, public_key)

Specs

encrypt(message :: String.t(), public_key :: term()) :: String.t()

Encrypt an arbitrary value to a Base64-encoded string

Examples

Utility.Crypto.encrypt("your secret message", public_key)
"MR0aLJrNNuQBqLPQgYtRNoA4WVBtj2B0aVwdH8Y6W+jU67JCD"

Specs

hash(String.t()) :: String.t()

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"
Link to this function

hmac(key, data)

Specs

hmac(String.t(), String.t()) :: String.t()

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"
Link to this function

letter_hash(data, args \\ [])

Specs

letter_hash(String.t(), Keyword.t()) :: String.t()

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; see hmac/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: "🍱🗿🦅🤖")
"🍱🍱🍱🤖🤖🗿🦅"
Link to this function

random_string(length \\ 64)

Specs

random_string(integer()) :: String.t()

Generate a random string. Sometimes you need those!

Examples

iex> Crypto.random_string(6) |> String.length()
6