Current section
Files
Jump to
Current section
Files
lib/cryppo/cli.ex
defmodule Cryppo.Cli do
@moduledoc false
use ExCLI.DSL, escript: true
alias Cryppo.{
EncryptedData,
EncryptedDataWithDerivedKey,
EncryptionKey,
Rsa4096,
RsaSignature
}
name("cryppo")
description("cryppo CLI")
command :help do
description("Display help for a cryppo command")
argument(:cmd)
run context do
case context[:cmd] do
"genkey" -> genkey_help()
"genkeypair" -> genkeypair_help()
"sign" -> sign_help()
"verify" -> verify_help()
"encrypt" -> encrypt_help()
"decrypt" -> decrypt_help()
"encrypt-der" -> encrypt_der_help()
"decrypt-der" -> decrypt_der_help()
unknown_cmd -> IO.puts("Unknown command: #{unknown_cmd}")
end
end
end
command :genkey do
description("Generate an encryption key for a symmetric encryption strategy")
option(:help, type: :boolean, aliases: [:h])
option(:strategy, default: "Aes256Gcm", aliases: [:s])
run context do
if context[:help],
do: genkey_help(),
else: genkey(context[:strategy])
end
end
defp genkey_help do
IO.puts("""
Generate an encryption key for a symmetric encryption strategy - printed as base64 encoded
USAGE
cryppo genkey -s [ENCRYPTION_STRATEGY]
OPTIONS
-s, --strategy=strategy encryption strategy (defaults to Aes256Gcm)
EXAMPLES
cryppo genkey
cryppo genkey --strategy=Aes256Gcm
""")
end
command :genkeypair do
description("Generate a new RSA key pair, writing the private and public keys to files")
option(:privateKeyOut, aliases: [:p])
option(:publicKeyOut, aliases: [:P])
run context do
if context[:privateKeyOut] && context[:publicKeyOut] do
genkeypair(context[:privateKeyOut], context[:publicKeyOut])
else
genkeypair_help()
end
end
end
defp genkeypair_help do
IO.puts("""
Generate a new RSA key pair, writing the private and public keys to files.
USAGE
cryppo genkeypair -p [PRIVATE_KEY_FILE] -P [PUBLIC_KEY_FILE]
OPTIONS
-p, --privateKeyOut=privateKeyOut (required) Private key output path
-P, --publicKeyOut=publicKeyOut (required) Public key output path
EXAMPLE
cryppo genkeypair -p private.pem -P public.pem
""")
end
command :sign do
description("Sign a file with an RSA private key and write the signed contents to a new file")
option(:privateKeyFile, aliases: [:p])
argument(:file_to_sign)
argument(:destination)
run context do
if context[:privateKeyFile] && context[:file_to_sign] && context[:destination] do
sign(context[:privateKeyFile], context[:file_to_sign], context[:destination])
else
sign_help()
end
end
end
defp sign_help do
IO.puts("""
USAGE
cryppo sign -p [PRIVATE_KEY_FILE] FILE DESTINATION
ARGUMENTS
FILE File to sign
DESTINATION file to write the resulting signed content to
OPTIONS
-p, --privateKeyFile=privateKeyFile (required) path to the private key file
EXAMPLE
cryppo sign -p private.pem my_file.txt my_file.signed.txt
""")
end
command :verify do
description("Verify an RSA signed file and write the contents to another file")
option(:publicKeyFile, aliases: [:P])
argument(:file_to_verify)
argument(:destination)
run context do
if context[:publicKeyFile] && context[:file_to_verify] && context[:destination] do
verify(context[:publicKeyFile], context[:file_to_verify], context[:destination])
else
verify_help()
end
end
end
defp verify_help do
IO.puts("""
USAGE
cryppo verify -P [PUBLIC_KEY_FILE] FILE DESTINATION
ARGUMENTS
FILE Signed file contents to verify
DESTINATION File to write the resulting verified content to
OPTIONS
-P, --publicKeyFile=publicKeyFile (required) path to the public key file
EXAMPLE
cryppo verify -P public.pem my_file.signed.txt my_file.txt
""")
end
command :encrypt do
description("Encrypt a serialized encrypted value")
option(:publicKeyFile, aliases: [:P])
option(:key, aliases: [:k])
option(:strategy, aliases: [:s])
option(:value, aliases: [:v])
run context do
public_key_file = context[:publicKeyFile]
key = context[:key]
strategy = context[:strategy]
data = context[:value]
cond do
data == nil ->
encrypt_help()
public_key_file && key ->
IO.puts(:stderr, "Either `key` or `publicKeyFile` must be present!\n")
encrypt_help()
strategy && public_key_file ->
IO.puts(:stderr, "Option `strategy` must only be used with `key`!\n")
encrypt_help()
key == nil && public_key_file == nil ->
encrypt_help()
true ->
if key do
strategy = if strategy, do: strategy, else: "Aes256Gcm"
encrypt(data, key, strategy)
else
if File.exists?(public_key_file),
do: encrypt(data, public_key_file),
else: IO.puts(:stderr, "File #{public_key_file} does not exist")
end
end
end
end
defp encrypt_help do
IO.puts("""
USAGE
cryppo encrypt -v [DATA] -k [KEY] -s [ENCRYPTION_STRATEGY]
cryppo encrypt -v [DATA] -P [PUBLIC_KEY_FILE]
OPTIONS
-v, --value=value (required) value to encrypt
-s, --strategy=strategy encryption strategy (defaults to Aes256Gcm)
-k, --key=key base64 encoded data encryption key
-P, --publicKeyFile=publicKeyFile public key file (if encrypting with RSA)
EXAMPLES
encrypt -v "hello world" -k vm8CjugMda2zdjsI9W25nH-CY-84DDYoBxTFLwfKLDk= -s Aes256Gcm
encrypt -v "hello world" -P public.pem
""")
end
command :decrypt do
description("Decrypt a serialized encrypted value")
option(:strategy, aliases: [:s])
option(:key, aliases: [:k])
option(:privateKeyFile, aliases: [:p])
option(:encrypted, aliases: [:e])
run context do
private_key_file = context[:privateKeyFile]
key = context[:key]
strategy = context[:strategy]
data = context[:encrypted]
cond do
data == nil ->
decrypt_help()
private_key_file && key ->
IO.puts(:stderr, "Either `key` or `privateKeyFile` must be present!\n")
encrypt_help()
strategy && private_key_file ->
IO.puts(:stderr, "Option `strategy` must only be used with `key`!\n")
encrypt_help()
key == nil && private_key_file == nil ->
encrypt_help()
true ->
if key do
strategy = if strategy, do: strategy, else: "Aes256Gcm"
decrypt(data, key, strategy)
else
if File.exists?(private_key_file),
do: decrypt(data, private_key_file),
else: IO.puts(:stderr, "File #{private_key_file} does not exist")
end
end
end
end
defp decrypt_help do
IO.puts("""
USAGE
cryppo decrypt -s [ENCRYPTED_DATA] -k [KEY] -s [ENCRYPTION_STRATEGY]
cryppo decrypt -s [ENCRYPTED_DATA] -p [PRIVATE_KEY_FILE]
OPTIONS
-s, --strategy=strategy encryption strategy (defaults to Aes256Gcm)
-k, --key=key base64 encoded data encryption key
-p, --privateKeyFile=privateKeyFile private key file (if encrypting with RSA)
-e, --encrypted=encrypted (required) serialized encrypted value
EXAMPLES
cryppo decrypt -e
"Aes256Gcm.gSAByGMq4edzM0U=.LS0tCml2OiAhYmluYXJ5IHwtCiAgaW1QL09qMWZ6eWw0cmwwSgphdDogIWJpbmFyeSB8LQogIE5SbjZUQXJ2bitNS1
Z5M0FpZEpmWlE9PQphZDogbm9uZQo=" -k vm8CjugMda2zdjsI9W25nH-CY-84DDYoBxTFLwfKLDk=
cryppo decrypt -e "Rsa4096.bJjV2g_RBZKeyqBr-dSjPAc3qtkTgd0=.LS0tCnt9Cg==" -p private.pem
""")
end
command :"encrypt-der" do
description("Encrypt data with a derived key")
option(:value, aliases: [:v])
option(:password, aliases: [:w])
option(:strategy, aliases: [:s])
option(:"derivation-strategy", aliases: [:d])
run context do
data = context[:value]
passphrase = context[:password]
strategy = context[:strategy]
derivation_strategy = context[:"derivation-strategy"]
cond do
data == nil ->
encrypt_der_help()
passphrase == nil ->
encrypt_der_help()
true ->
strategy = if strategy, do: strategy, else: "Aes256Gcm"
derivation_strategy =
if derivation_strategy, do: derivation_strategy, else: "Pbkdf2Hmac"
encrypt_with_derived_key(data, strategy, derivation_strategy, passphrase)
end
end
end
defp encrypt_der_help do
IO.puts("""
USAGE
cryppo encrypt-der -v [DATA] -w [PASSWORD] -s [ENCRYPTION_STRATEGY] -d [DERIVATION_STRATEGY]
OPTIONS
-v, --value=value (required) value to encrypt
-w, --password=password (required) password for key derivation
-s, --strategy=strategy encryption strategy (defaults to Aes256Gcm)
-d, --derivation-strategy=strategy derivation strategy (defaults to Pbkdf2Hmac)
EXAMPLES
cryppo encrypt-der -v "hello world" -w "secret phrase" -s Aes256Gcm -d Pbkdf2Hmac
cryppo encrypt-der -v "hello world" -w "secret phrase"
""")
end
command :"decrypt-der" do
description("Encrypt data with a derived key")
option(:encrypted, aliases: [:e])
option(:password, aliases: [:w])
run context do
data = context[:encrypted]
passphrase = context[:password]
cond do
data == nil ->
decrypt_der_help()
passphrase == nil ->
decrypt_der_help()
true ->
decrypt_with_derived_key(data, passphrase)
end
end
end
defp decrypt_der_help do
IO.puts("""
USAGE
cryppo decrypt-der -e [ENCRYPTED_DATA] -w [PASSWORD]
OPTIONS
-e, --encrypted=encrypted (required) serialized encrypted value
-w, --password=password (required) passphrase for key derivation
EXAMPLES
cryppo decrypt-der -w "secret phrase" \\
-e "Aes256Gcm.e-IJT9E8ew3wlz8=.LS0tCmFkOiBub25lCmF0OiAhIWJpbmFyeSB8LQogIHpTRzQzbVhlSFBsR3ZQQVZoNTVJQUE9PQppdjogISFiaW5hcnkgfC0KICBMU2NDNmVCZ2wrUCtuUkpaCg==.Pbkdf2Hmac.LS0tCidpJzogMjEzMjIKJ2l2JzogISFiaW5hcnkgfC0KICBzTmlGT21xWEg5b1piNzRNVElCcGxvNHlHV2M9CidsJzogMzIK"
""")
end
defp decrypt(data, private_key_file) do
with {:ok, pem} <- File.read(private_key_file),
{:ok, key} <- Rsa4096.from_pem(pem),
%EncryptionKey{key: rsa_key}
when elem(rsa_key, 0) == :RSAPrivateKey and tuple_size(rsa_key) == 11 <-
key do
decrypt(data, key, "Rsa4096")
else
%EncryptionKey{key: rsa_key}
when elem(rsa_key, 0) == :RSAPublicKey and tuple_size(rsa_key) == 3 ->
IO.puts(:stderr, "A private RSA key is required for decryption, not a public key")
err ->
IO.puts(:stderr, inspect(err))
end
end
defp decrypt(data, %EncryptionKey{} = key, strategy) do
IO.puts("Encryption strategy: #{strategy}")
with {:ok, encrypted = %EncryptedData{}} <- Cryppo.load(data),
{:ok, decrypted} <- Cryppo.decrypt(encrypted, key) do
IO.puts(decrypted)
else
err -> IO.puts(:stderr, inspect(err))
end
end
defp decrypt(data, key_base64, strategy) do
case Base.url_decode64(key_base64) do
{:ok, key} -> decrypt(data, EncryptionKey.new(key), strategy)
_ -> IO.puts(:stderr, "The key is invalid base64!")
end
end
defp genkey(strategy) do
case Cryppo.generate_encryption_key(strategy) do
%EncryptionKey{key: key} ->
IO.puts("Encryption strategy: #{strategy}")
key |> Base.url_encode64(padding: true) |> IO.puts()
err ->
IO.puts(:stderr, inspect(err))
end
end
defp genkeypair(private_key_out, public_key_out) do
cond do
private_key_out == public_key_out ->
IO.puts(:stderr, "publicKeyOut and privateKeyOut are the same file!")
File.exists?(private_key_out) ->
IO.puts(:stderr, "File #{private_key_out} already exists, exiting...")
File.exists?(public_key_out) ->
IO.puts(:stderr, "File #{public_key_out} already exists, exiting...")
true ->
with private_key = %EncryptionKey{} <- Cryppo.generate_encryption_key("Rsa4096"),
public_key = {:RSAPublicKey, _, _} <- Rsa4096.private_key_to_public_key(private_key),
{:ok, private_key_pem} <- Rsa4096.to_pem(private_key),
{:ok, public_key_pem} <- Rsa4096.to_pem(public_key),
{:ok, private_key_file} <- File.open(private_key_out, [:write]),
{:ok, public_key_file} <- File.open(public_key_out, [:write]),
:ok <- IO.write(private_key_file, private_key_pem),
:ok <- IO.write(public_key_file, public_key_pem),
:ok <- File.close(private_key_file),
:ok <- File.close(public_key_file) do
"Private key written to #{private_key_out}, public key written to #{public_key_out}."
|> IO.puts()
else
err -> IO.puts(:stderr, inspect(err))
end
end
end
defp sign(private_key_pem_file, file_to_sign, destination) do
cond do
!File.exists?(private_key_pem_file) ->
IO.puts(:stderr, "File #{private_key_pem_file} not found...")
!File.exists?(file_to_sign) ->
IO.puts(:stderr, "File #{file_to_sign} not found...")
File.exists?(destination) ->
IO.puts(:stderr, "File #{destination} already exists, exiting...")
true ->
with {:ok, pem} <- File.read(private_key_pem_file),
{:ok, data_to_sign} <- File.read(file_to_sign),
{:ok, key} <- Rsa4096.from_pem(pem),
signature = %RsaSignature{} <- Rsa4096.sign(data_to_sign, key),
serialized_signature <- Cryppo.serialize(signature),
{:ok, destination_file} <- File.open(destination, [:write]),
:ok <- IO.write(destination_file, serialized_signature),
:ok <- File.close(destination_file) do
IO.puts("Signature written to file #{destination}")
else
err -> IO.puts(:stderr, inspect(err))
end
end
end
defp verify(public_key_pem_file, file_to_verify, destination) do
cond do
!File.exists?(public_key_pem_file) ->
IO.puts(:stderr, "File #{public_key_pem_file} not found...")
!File.exists?(file_to_verify) ->
IO.puts(:stderr, "File #{file_to_verify} not found...")
File.exists?(destination) ->
IO.puts(:stderr, "File #{destination} already exists, exiting...")
true ->
with {:ok, pem} <- File.read(public_key_pem_file),
{:ok, data_to_verify_serialized} <- File.read(file_to_verify),
{:ok, key} <- Rsa4096.from_pem(pem),
{:ok, rsa_signature = %RsaSignature{}} <- Cryppo.load(data_to_verify_serialized) do
if Rsa4096.verify(rsa_signature, key) do
IO.puts("Data signature verified")
with {:ok, destination_file} <- File.open(destination, [:write]),
:ok <- IO.write(destination_file, rsa_signature.data),
:ok <- File.close(destination_file) do
IO.puts("Data written to file #{destination}")
else
err -> IO.puts(:stderr, inspect(err))
end
else
IO.puts("Data could not be verified")
end
else
err -> IO.puts(:stderr, inspect(err))
end
end
end
defp encrypt(data, %EncryptionKey{} = key, strategy) do
IO.puts("Encryption strategy: #{strategy}")
data |> Cryppo.encrypt(strategy, key) |> Cryppo.serialize() |> IO.puts()
end
defp encrypt(data, key_base64, strategy) do
case Base.url_decode64(key_base64) do
{:ok, key} -> encrypt(data, EncryptionKey.new(key), strategy)
_ -> IO.puts(:stderr, "The key is invalid base64!")
end
end
defp encrypt(data, public_key_file) do
with {:ok, pem} <- File.read(public_key_file),
{:ok, key} <- Rsa4096.from_pem(pem) do
encrypt(data, key, "Rsa4096")
else
err -> IO.puts(:stderr, inspect(err))
end
end
defp encrypt_with_derived_key(data, strategy, derivation_strategy, passphrase) do
case Cryppo.encrypt_with_derived_key(data, strategy, derivation_strategy, passphrase) do
encrypted = %EncryptedDataWithDerivedKey{} ->
encrypted |> Cryppo.serialize() |> IO.puts()
err ->
IO.puts(:stderr, inspect(err))
end
end
defp decrypt_with_derived_key(data, passphrase) do
with {:ok, encrypted = %EncryptedDataWithDerivedKey{}} <- Cryppo.load(data),
{:ok, decrypted, _key} <- Cryppo.decrypt_with_derived_key(encrypted, passphrase) do
IO.puts(decrypted)
else
err -> IO.puts(:stderr, inspect(err))
end
end
end