ntrepid8 / ex_crypto Goto Github PK
View Code? Open in Web Editor NEWWrapper around the Erlang crypto module for Elixir.
License: MIT License
Wrapper around the Erlang crypto module for Elixir.
License: MIT License
It would be nice if all public functions had a @spec
type specification so it becomes more obvious from the documentation what types can be given to a function without reading the actual code.
This would also make it possible for dialyzer to do type inference on the code, e.g. I just came across an issue where a RSAPrivateKey
was given to ExPublicKey.encrypt_public
but the code ended up failing inside ExPublicKey.RSAPublicKey
(https://github.com/ntrepid8/ex_crypto/blob/master/lib/ex_public_key/ex_rsa_public_key.ex#L31) with the error
** (Protocol.UndefinedError) protocol String.Chars not implemented for #ExPublicKey.RSAPrivateKey<fingerprint_sha256=....>. This protocol is implemented for: Ecto.Time, Ecto.DateTime, Ecto.Date, Decimal, Version, Atom, Integer, Version.Requirement, NaiveDateTime, URI, Date, DateTime, BitString, List, Float, Time
because the line
{:error, "invalid ExPublicKey.RSAPublicKey: #{rsa_public_key}"}
did not use #{inspect rsa_public_key}
.
Straight from the docs:
{:ok, key} = ExPublicKey.generate_key(:rsa, 2048)
But this is what I get when running it:
** (ArgumentError) argument error
(crypto) :crypto.rsa_generate_key_nif(:rsa, <<8, 0>>)
(crypto) crypto.erl:543: :crypto.generate_key/3
(public_key) public_key.erl:426: :public_key.generate_key/1
(ex_crypto) lib/ex_public_key.ex:287: ExPublicKey.generate_key/4
Using 0.9.0.
For example, if you provide a different Auth data for decryption:
iex> clear_text = "my-clear-text"
iex> auth_data = "my-auth-data"
iex> bad_auth_data = "bad-auth-data"
iex> {:ok, aes_256_key} = ExCrypto.generate_aes_key(:aes_256, :bytes)
iex> {:ok, {_ad, payload}} = ExCrypto.encrypt(aes_256_key, auth_data, clear_text)
iex> {init_vec, cipher_text, cipher_tag} = payload
iex> {:ok, :error} = ExCrypto.decrypt(aes_256_key, bad_auth_data, init_vec, cipher_text, cipher_tag)
Hi,
what's the reason to use url_decode64 and not decode64?
https://github.com/ntrepid8/ex_crypto/blob/master/lib/ex_public_key.ex#L166
This is the same issue as reported here:
https://stackoverflow.com/questions/41336947/erlang-generate-rsa-keys-from-pem-files
In this case it causes ExPublicKey.load/2
to return an {:error, _} tuple.
Loading the corresponding public key produces the :RSAPublicKey
as expected.
I'm using OpenSSL 1.0.2o
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:65537 -out test_private.pem
openssl pkey -pubout -inform PEM -outform PEM -in test_private.pem -out test_public.pem
iex(1)> ExPublicKey.load("test_public.pem")
{:ok, #ExPublicKey.RSAPublicKey<
fingerprint_sha256=
a5...>}
iex(2)> ExPublicKey.load("test_private.pem")
{:error,
"invalid argument, expected one of[ExPublicKey.RSAPublicKey, ExPublicKey.RSAPrivateKey], found: PrivateKeyInfo"}
Use :public_key.der_decode/2
with the data provided from the PrivateKeyInfo
tuple. I'm assuming the the algorithm identifier is contained inside the PrivateKeyInfo
data, but I don't know how to extract it so I've hard-coded :RSAPrivateKey
here.
defmodule Key do
def load_priv_key() do
{:PrivateKeyInfo, :v1, _, private_key_binary, _} =
File.read!("test_private.pem")
|> :public_key.pem_decode() |> hd
|> :public_key.pem_entry_decode()
:public_key.der_decode(:RSAPrivateKey, private_key_binary)
|> ExPublicKey.RSAPrivateKey.from_sequence()
end
end
iex(1)> Key.load_priv_key
#ExPublicKey.RSAPrivateKey<
fingerprint_sha256=
a5...>
I'm trying to do basic AES encryption on some text
{:ok, aes_128_key} = ExCrypto.generate_aes_key(:aes_128, :bytes)
{:ok, {_iv, cipher_text}} = ExCrypto.encrypt(aes_128_key, "hi")
and I'm getting this error
** (MatchError) no match of right hand side value: %ErlangError{original: :notsup}
(ex_crypto) lib/ex_crypto.ex:28: ExCrypto.normalize_error/3
If I use aes_256 everything works.
Hi there,
I've found your very useful Elixir lib but I'm facing with an issue when I'm using AES-256-CBC private keys
Test case that fails:
test/ex_public_key_test.exs
insert at line : 32
"-aes256",
so instead of
# generate a passphrase protected RSA private key with openssl
System.cmd("openssl", [
"genrsa",
"-out",
rsa_secure_private_key_path,
"-passout",
"pass:#{rand_string}",
"2048"
])
we have now
# generate a passphrase protected RSA private key with openssl
System.cmd("openssl", [
"genrsa",
"-aes256",
"-out",
rsa_secure_private_key_path,
"-passout",
"pass:#{rand_string}",
"2048"
])
and the test
mix test test/ex_public_key_test.exs:79
fails
1) test read secure RSA keys (ExPublicKeyTest)
test/ex_public_key_test.exs:79
** (CaseClauseError) no case clause matching: {:error, %FunctionClauseError{args: nil, arity: 4, clauses: nil, function: :decode, kind: nil, module: :pubkey_pbe}, [{:pubkey_pbe, :decode, [<<43, 180, 39, 134, 179, 160, 115, 204, 207, 162, 100, 17, 226, 84, 197, 254, 3, 10, 68, 212, 144, 49, 120, 150, 239, 201, 223, 214, 213, 157, 39, 151, 17, 109, 42, 106, 193, 217, 176, 117, 244, 187, ...>>, 'KLvk', 'AES-256-CBC', <<246, 36, 62, 81, 225, 191, 64, 102, 55, 197, 38, 121, 69, 244, 140, 46>>], [file: 'pubkey_pbe.erl', line: 59]}, {:public_key, :do_pem_entry_decode, 2, [file: 'public_key.erl', line: 1103]}, {ExPublicKey, :load_pem_entry, 2, [file: 'lib/ex_public_key.ex', line: 128]}, {ExPublicKey, :load_pem_entry, 2, [file: 'lib/ex_public_key.ex', line: 122]}, {ExPublicKey, :loads, 2, [file: 'lib/ex_public_key.ex', line: 94]}, {ExPublicKey, :loads!, 2, [file: 'lib/ex_public_key.ex', line: 107]}, {ExPublicKeyTest, :"test read secure RSA keys", 1, [file: 'test/ex_public_key_test.exs', line: 81]}, {ExUnit.Runner, :exec_test, 1, [file: 'lib/ex_unit/runner.ex', line: 306]}, {:timer, :tc, 1, [file: 'timer.erl', line: 166]}, {ExUnit.Runner, :"-spawn_test/3-fun-1-", 4, [file: 'lib/ex_unit/runner.ex', line: 245]}]}
code: secure_rsa_priv_key = ExPublicKey.loads!(secure_priv_key_string, context[:passphrase])
stacktrace:
(ex_crypto) lib/ex_public_key.ex:107: ExPublicKey.loads!/2
test/ex_public_key_test.exs:81: (test)
if you replace "-aes256" with "-aes128" . test/ex_public_key_test.exs is OK
Any idea?
Many thanks in advance
Daniel
In http://erlang.org/doc/apps/crypto/new_api.html it is recommended that the "new API", with functions such as crypto_one_time/4
, be used over the "old API", with functions such as block_encrypt/4
. The old functions might be deprecated in the future.
ex_crypto
uses the old API in its implementations of the encrypt
function, for example.
iex(4)> System.otp_release()
"24"
iex(5)> clear_text = "my-clear-text"
"my-clear-text"
iex(6)> auth_data = "my-auth-data"
"my-auth-data"
iex(7)> {:ok, aes_256_key} = ExCrypto.generate_aes_key(:aes_256, :bytes)
{:ok,
<<16, 212, 156, 208, 202, 106, 240, 39, 18, 138, 213, 17, 35, 15, 50, 53, 156,
60, 141, 243, 97, 61, 145, 27, 178, 31, 106, 172, 167, 224, 213, 119>>}
iex(8)> {:ok, {ad, {init_vec, cipher_text, cipher_tag}}} = ExCrypto.encrypt(aes_256_key, auth_data, clear_text)
** (UndefinedFunctionError) function :crypto.block_encrypt/4 is undefined or private, use crypto:crypto_one_time/5, crypto:crypto_one_time_aead/6,7 or crypto:crypto(dyn_iv)?init + crypto:crypto(dyn_iv)?_update + crypto:crypto_final instead
(crypto 5.0.2) :crypto.block_encrypt(:aes_gcm, <<16, 212, 156, 208, 202, 106, 240, 39, 18, 138, 213, 17, 35, 15, 50, 53, 156, 60, 141, 243, 97, 61, 145, 27, 178, 31, 106, 172, 167, 224, 213, 119>>, <<75, 167, 47, 63, 228, 39, 177, 233, 98, 107, 47, 119, 119, 193, 82, 54>>, {"my-auth-data", "my-clear-text"})
(ex_crypto 0.10.0) lib/ex_crypto.ex:317: ExCrypto._encrypt/4
I'm struggling currently to get clean, easy encryption interoperability between ruby and elixir, which looks easy on paper as both use OpenSSL under the hood, but think elixir community could use some better abstractions around Erlang crypto, which looks like exactly what this library is out to offer!
I got a simple zero IV working using this code:
https://gist.github.com/bglusman/7e1df9efad80845977b60d44d335f3df
But it's brittle and awkward and I thought perhaps we can include some elixir version here that will:
a) help avoid invalid IV values
b) perhaps provide easy options to decrypt existing widely used options from ruby and/or other standard libraries
In ruby we're using attr_encrypted for DB encryption, though still currently stuck on 1.4 until we can find time to use explicit IV's instead of "single_iv_and_salt" which I think corresponds to using pkcs5_keyivgen in ruby OpenSSL, but I'm not 100% sure. Being able to support both that and explicit IV's would be nice, but I also wasted a lot of time not realizing that all IV's had to be 128 bits which a good error message would help, and whatever I've screwed up now that erlang just says "** (ErlangError) erlang error: :notsup" in response to should also hopefully be able to have a more illuminating/useful error message as well :-)
Folks in the #security channel on elixir slack may be up for helping, they helped me past the 128 bit issue, I'll post this issue there as well.
I'm trying to understand what auth_data for GCM mode is. Between https://crypto.stackexchange.com/questions/35727/does-aad-make-gcm-encryption-more-secure which says that it's optional, and https://en.wikipedia.org/wiki/Galois/Counter_Mode has this blurb: As with any message authentication code, if the adversary chooses a t-bit tag at random, it is expected to be correct for given data with probability 2โt. With GCM, however, an adversary can choose tags that increase this probability, proportional to the total length of the ciphertext and additional authenticated data (AAD). Consequently, GCM is not well-suited for use with very short tag lengths or very long messages.
Basically, can I omit this data if I control my metadata elsewhere (e.g. protocol negotiation takes place elsewhere)?
Either way, can you update the documentation to be a little more descriptive here? The samples just have "other auth data" which doesn't help me understand how it could be used practically.
I have RSA keys that are encrypted using a password. How would I go about loading them from disk and using them? Currently, when trying to load them, gives this error:
iex(11)> ExPublicKey.load("/Users/Psy/crap/crypto/testkey.pem")
{:error,
%FunctionClauseError{arity: 1, function: :pem_entry_decode,
module: :public_key},
[{:public_key, :pem_entry_decode,
[{:RSAPrivateKey,
<<69, 103, 57, 185, 26, 114, 20, 34, 100, 84, 103, 123, 157, 128, 197, 116,
174, 125, 94, 20, 234, 243, 138, 198, 8, 108, 207, 220, 65, 70, 93, 198,
113, 84, 163, 100, 154, 48, 60, 9, 237, 3, ...>>,
{'AES-128-CBC',
<<44, 86, 151, 113, 171, 22, 39, 28, 21, 99, 173, 229, 4, 100, 138,
3>>}}], [file: 'public_key.erl', line: 140]},
{ExPublicKey, :load_pem_entry, 1, [file: 'lib/ex_public_key.ex', line: 63]},
{ExPublicKey, :loads, 1, [file: 'lib/ex_public_key.ex', line: 49]},
{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 670]},
{:elixir, :erl_eval, 3, [file: 'src/elixir.erl', line: 229]},
{:elixir, :eval_forms, 4, [file: 'src/elixir.erl', line: 217]},
{IEx.Evaluator, :handle_eval, 6, [file: 'lib/iex/evaluator.ex', line: 182]},
{IEx.Evaluator, :do_eval, 4, [file: 'lib/iex/evaluator.ex', line: 175]},
{IEx.Evaluator, :eval, 4, [file: 'lib/iex/evaluator.ex', line: 155]},
{IEx.Evaluator, :loop, 3, [file: 'lib/iex/evaluator.ex', line: 61]},
{IEx.Evaluator, :init, 4, [file: 'lib/iex/evaluator.ex', line: 21]},
{:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]}
The documentation shows
{:ok, aes_256_key} = ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok,
"Bs_BzhuwseEA8ZUvuEY0mq9Rmlv6cSoU_RaYD14Q62HiN_kJ4FiaW0YYppf1ffYPQ56xuitxQtYAnaeP-Q5l1WPh5aExdwCG_PUm5g-MlOUA1XSSP2RvuQqAiHzazIzjGVSIcl0Gr7TSLPOoIQrPshMNaA4j3SGZ3lAOqO1quvXtDn-9Sxwr5dwV7VzOIvXRwb0GbZeYp8lnVJgeqHl8cEhUTfT_h9Pm7tU2CFeHZCDK8ntFT_t4q6VlcBcvw_Pj3CGcVSmpmCHMKW1brt6jXGBijqSTdbjYDZnCx2Q44VoYqMMZ1U2GnVyjc-ZuwugwGGqQ7UEqV_TOMjbK6Oxx-Q=="}
But when I ran this locally, I get these much shorter results:
iex(1)> ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok, "7MCZHilcDU7C3JTIicel0HTbMdoewepNLbjf0Is5fpI="}
iex(2)> ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok, "8rPe6WBHKsWzRB1bTlwVyaMqNWmo1X9kO33l36Twc1I="}
iex(3)> ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok, "n4LPbbqZ2Zopp7XKM7WBlq-1CwHRU96ljkAm3y8S-tQ="}
iex(4)> ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok, "nNa1fpWJ7lqtXHbQ3dMLl5Fg8dyaJTdrPKl2iHaIzTg="}
iex(5)> ExCrypto.generate_aes_key(:aes_256, :base64)
{:ok, "FyoaY2gPzaz-3DEHdqtoArdp2lf203B0tuf5grYY_9w="}
The versions I have on my Mac 10.15.3 is:
$ elixir --version
Erlang/OTP 22 [erts-10.6.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Elixir 1.9.4 (compiled with Erlang/OTP 20)
$ openssl version
LibreSSL 2.8.3
What am I missing that these keys seem shorter than they should be.
Hello,
I got couple of warnings with Elixir 1.5.2 and version 0.7.1:
warning: clauses for the same def should be grouped together, def encrypt/3 was previously defined (lib/ex_crypto.ex:274)
lib/ex_crypto.ex:321
warning: crypto:rand_uniform/2 is deprecated and will be removed in a future release; use rand:uniform/1
lib/ex_crypto.ex:102
warning: clauses for the same def should be grouped together, def generate_key/3 was previously defined (lib/ex_public_key.ex:246)
lib/ex_public_key.ex:267
warning: default arguments in load_pem_entry/2 are never used
lib/ex_public_key.ex:112
warning: this clause cannot match because a previous clause at line 244 always matches
lib/ex_public_key.ex:245
warning: variable "public_exp" is unused
lib/ex_public_key.ex:247
I found a possible bug on ExPublicKey.encrypt_private/3
.
iex> System.otp_release()
"24"
iex> short_text = "Hello"
"Hello"
iex> long_text = String.duplicate("a", 10000)
"aaa..."
iex> rsa_priv_key = ExPublicKey.load!("/path/to/private_key.pem")
#ExPublicKey.RSAPrivateKey<...>
iex> ExPublicKey.encrypt_private(short_text, rsa_priv_key)
{:ok,
"hlnaQvo5Onskl1dlI95RSoBAZlUDVMfHXmM5J3nuB2D7er02AivkOz2l9POaH8KgN4KbVFzbnla4-i8YUmWrOQ=="}
iex> ExPublicKey.encrypt_private(long_text, rsa_priv_key)
{:error, %ErlangError{original: :encrypt_failed}, []}
In fact, an error occurs even if the text is not so long. For example, replacing 10000 with 100 causes the same problem.
However, if I replace 10000 with 50, the error does not occur.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.