diff --git a/include/enoise.hrl b/include/enoise.hrl index d96b971..2c40c46 100644 --- a/include/enoise.hrl +++ b/include/enoise.hrl @@ -10,12 +10,7 @@ -record(key_pair, { puk, pik }). --record(noise_ss, { cs :: enoise_cipher_state:state() - , ck = <<>> :: binary() - , h = <<>> :: binary() - , hash = blake2b }). - --record(noise_hs, { ss :: #noise_ss{} | undefined +-record(noise_hs, { ss :: enoise_sym_state: state() , s :: #key_pair{} | undefined , e :: #key_pair{} | undefined , rs :: binary() | undefined diff --git a/src/enoise_cipher_state.erl b/src/enoise_cipher_state.erl index 9ebac6b..b1b34e7 100644 --- a/src/enoise_cipher_state.erl +++ b/src/enoise_cipher_state.erl @@ -9,6 +9,7 @@ , encrypt_with_ad/3 , has_key/1 , init/2 + , key/1 , rekey/1 , set_key/2 , set_nonce/2 @@ -70,3 +71,7 @@ rekey(CState = #noise_cs{ k = K, cipher = Cipher }) -> -spec cipher(CState :: state()) -> noise_cipher(). cipher(#noise_cs{ cipher = Cipher }) -> Cipher. + +-spec key(CState :: state()) -> key(). +key(#noise_cs{ k = K }) -> + K. diff --git a/src/enoise_crypto.erl b/src/enoise_crypto.erl index ac37e35..7e179c3 100644 --- a/src/enoise_crypto.erl +++ b/src/enoise_crypto.erl @@ -6,7 +6,7 @@ -include("enoise.hrl"). --export([decrypt/5, encrypt/5, rekey/2, hash/2, pad/3, hashlen/1, dhlen/1, new_key_pair/1, hkdf/3, dh/3]). +-export([decrypt/5, encrypt/5, rekey/2, hash/2, pad/3, hashlen/1, dhlen/1, new_key_pair/1, hmac/3, hkdf/3, dh/3]). new_key_pair(dh25519) -> KeyPair = enacl:crypto_sign_ed25519_keypair(), @@ -16,7 +16,19 @@ new_key_pair(dh25519) -> dh(dh25519, KeyPair, PubKey) -> enacl:curve25519_scalarmult(KeyPair#key_pair.pik, PubKey). -hkdf(_, _, _) -> []. +hmac(Hash, Key, Data) -> + BLen = blocklen(blake2b), + Block1 = hmac_format_key(Hash, Key, 16#36, BLen), + Hash1 = hash(Hash, <>), + Block2 = hmac_format_key(Hash, Key, 16#5C, BLen), + hash(Hash, <>). + +hkdf(Hash, Key, Data) -> + TempKey = hmac(Hash, Key, Data), + Output1 = hmac(Hash, TempKey, <<1:8>>), + Output2 = hmac(Hash, TempKey, <>), + Output3 = hmac(Hash, TempKey, <>), + [Output1, Output2, Output3]. rekey(Cipher, K) -> encrypt(Cipher, K, ?MAX_NONCE, <<>>, <<0:(32*8)>>). @@ -32,9 +44,9 @@ decrypt('ChaChaPoly', K, N, Ad, CipherText) -> enacl:aead_chacha20poly1305_decrypt(K, N, Ad, CipherText). hash(blake2b, Data) -> - enacl:generichash(64, Data); -hash(blake2s, Data) -> - enacl:generichash(32, Data). + {ok, Hash} = enacl:generichash(64, Data), Hash; +hash(Hash, _Data) -> + error({hash_not_implemented_yet, Hash}). pad(Data, MinSize, PadByte) -> case byte_size(Data) of @@ -50,5 +62,24 @@ hashlen(sha512) -> 64; hashlen(blake2s) -> 32; hashlen(blake2b) -> 64. +blocklen(sha256) -> 64; +blocklen(sha512) -> 128; +blocklen(blake2s) -> 64; +blocklen(blake2b) -> 128. + dhlen(dh25519) -> 32; dhlen(dh448) -> 56. + +%%% Local implementations + + +hmac_format_key(Hash, Key0, Pad, BLen) -> + Key1 = + case byte_size(Key0) =< BLen of + true -> Key0; + false -> hash(Hash, Key0) + end, + Key2 = pad(Key1, BLen, 0), + <> = <>, + << <<(Word bxor PadWord):32>> || <> <= Key2 >>. + diff --git a/src/enoise_protocol.erl b/src/enoise_protocol.erl new file mode 100644 index 0000000..9a6c305 --- /dev/null +++ b/src/enoise_protocol.erl @@ -0,0 +1,12 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_protocol). + +-include("enoise.hrl"). + +-export([to_name/1]). + +to_name(_Protocol) -> + <<"Noise_XK_25519_ChaChaPoly_BLAKE2b">>. diff --git a/src/enoise_sym_state.erl b/src/enoise_sym_state.erl new file mode 100644 index 0000000..b3d0a31 --- /dev/null +++ b/src/enoise_sym_state.erl @@ -0,0 +1,101 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_sym_state). + +-export([ cipher_state/1 + , ck/1 + , decrypt_and_hash/2 + , encrypt_and_hash/2 + , h/1 + , hash/1 + , init/1 + , mix_hash/2 + , mix_key/2 + , mix_key_and_hash/2 + , split/1 + ]). + +-include("enoise.hrl"). + +-type noise_hash() :: sha256 | sha512 | blake2s | blake2b. + +-record(noise_ss, { cs :: enoise_cipher_state:state() + , ck = <<>> :: binary() + , h = <<>> :: binary() + , hash = blake2b :: noise_hash() }). + +-opaque state() :: #noise_ss{}. +-export_type([noise_hash/0, state/0]). + +-spec init(Protocol :: #noise_protocol{}) -> state(). +init(Protocol = #noise_protocol{ hash = Hash, cipher = Cipher }) -> + Name = enoise_protocol:to_name(Protocol), + HashLen = enoise_crypto:hashlen(Hash), + H1 = + case byte_size(Name) > HashLen of + true -> enoise_crypto:hash(Hash, Name); + false -> enoise_crypto:pad(Name, HashLen, 16#00) + end, + #noise_ss{ h = H1 + , ck = H1 + , hash = Hash + , cs = enoise_cipher_state:init(empty, Cipher) }. + +-spec mix_key(SState :: state(), InputKeyMaterial :: binary()) -> state(). +mix_key(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) -> + [CK1, <> | _] = + enoise_crypto:hkdf(Hash, CK0, InputKeyMaterial), + CS1 = enoise_cipher_state:set_key(CS0, TempK), + SState#noise_ss{ ck = CK1, cs = CS1 }. + +-spec mix_hash(SState :: state(), Data :: binary()) -> state(). +mix_hash(SState = #noise_ss{ hash = Hash, h = H0 }, Data) -> + H1 = enoise_crypto:hash(Hash, <>), + SState#noise_ss{ h = H1 }. + +-spec mix_key_and_hash(SState :: state(), InputKeyMaterial :: binary()) -> state(). +mix_key_and_hash(SState = #noise_ss{ hash = Hash, ck = CK0, cs = CS0 }, InputKeyMaterial) -> + [CK1, TempH, <>] = + enoise_crypto:hkdf(Hash, CK0, InputKeyMaterial), + CS1 = enoise_cipher_state:set_key(CS0, TempK), + mix_hash(SState#noise_ss{ ck = CK1, cs = CS1 }, TempH). + +-spec encrypt_and_hash(SState :: state(), PlainText :: binary()) -> {ok, state(), binary()}. +encrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, PlainText) -> + {ok, CS1, CipherText} = enoise_cipher_state:encrypt_with_ad(CS0, H, PlainText), + {ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), CipherText}. + +-spec decrypt_and_hash(SState :: state(), CipherText :: binary()) -> + {ok, state(), binary()} | {error, term()}. +decrypt_and_hash(SState = #noise_ss{ cs = CS0, h = H }, CipherText) -> + case enoise_cipher_state:decrypt_with_ad(CS0, H, CipherText) of + Err = {error, _} -> + Err; + {ok, CS1, PlainText} -> + {ok, mix_hash(SState#noise_ss{ cs = CS1 }, CipherText), PlainText} + end. + +-spec split(SState :: state()) -> {enoise_cipher_state:state(), enoise_cipher_state:state()}. +split(#noise_ss{ hash = Hash, ck = CK, cs = CS }) -> + [<>, <>, _] = + enoise_crypto:hkdf(Hash, CK, <<>>), + {enoise_cipher_state:set_key(CS, TempK1), + enoise_cipher_state:set_key(CS, TempK2)}. + +-spec cipher_state(SState :: state()) -> enoise_cipher_state:state(). +cipher_state(#noise_ss{ cs = CS }) -> + CS. + +-spec ck(SState :: state()) -> binary(). +ck(#noise_ss{ ck = CK }) -> + CK. + +-spec h(SState :: state()) -> binary(). +h(#noise_ss{ h = H }) -> + H. + +-spec hash(SState :: state()) -> noise_hash(). +hash(#noise_ss{ hash = Hash }) -> + Hash. diff --git a/test/enoise_crypto_tests.erl b/test/enoise_crypto_tests.erl index b3dc6d8..55f918f 100644 --- a/test/enoise_crypto_tests.erl +++ b/test/enoise_crypto_tests.erl @@ -28,3 +28,25 @@ chachapoly_test() -> ?assertMatch(PlainText, PlainText0), ok. +blake2b_test() -> + Test = fun(#{ input := In, output := Out }) -> + ?assertMatch(Out, enoise_crypto:hash(blake2b, In)) + end, + lists:foreach(Test, test_utils:blake2b_data()). + +%% blake2s_test() -> +%% #{ input := In, output := Out } = test_utils:blake2s_data(), +%% ?assertMatch(Out, enoise_crypto:hash(blake2s, In)). + +blake2b_hmac_test() -> + Test = fun(#{ key := Key, data := Data, hmac := HMAC }) -> + ?assertMatch(HMAC, enoise_crypto:hmac(blake2b, Key, Data)) + end, + lists:foreach(Test, test_utils:blake2b_hmac_data()). + +blake2b_hkdf_test() -> + Test = fun(#{ key := Key, data := Data, out1 := Out1, out2 := Out2 }) -> + ?assertMatch([Out1, Out2, _], enoise_crypto:hkdf(blake2b, Key, Data)) + end, + lists:foreach(Test, test_utils:blake2b_hkdf_data()). + diff --git a/test/enoise_sym_state_tests.erl b/test/enoise_sym_state_tests.erl new file mode 100644 index 0000000..fc63931 --- /dev/null +++ b/test/enoise_sym_state_tests.erl @@ -0,0 +1,60 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%%------------------------------------------------------------------- + +-module(enoise_sym_state_tests). + +-include_lib("eunit/include/eunit.hrl"). +-include("enoise.hrl"). + +noise_XK_25519_ChaChaPoly_Blake2b_test() -> + Protocol = #noise_protocol{ hs_pattern = noiseXK + , dh = dh25519 + , cipher = 'ChaChaPoly' + , hash = blake2b }, + + SSE0 = enoise_sym_state:init(Protocol), + SSD0 = enoise_sym_state:init(Protocol), + + Name = enoise_protocol:to_name(Protocol), + PadName = enoise_crypto:pad(Name, enoise_crypto:hashlen(blake2b), 0), + + ?assertMatch(PadName, enoise_sym_state:h(SSE0)), + ?assertMatch(PadName, enoise_sym_state:ck(SSE0)), + ?assertMatch(false, enoise_cipher_state:has_key(enoise_sym_state:cipher_state(SSE0))), + + TestBin = h2b("0x6162636465666768696A6B6C6D6E6F707172737475767778797A"), + SSE1 = enoise_sym_state:mix_hash(SSE0, TestBin), + SSD1 = enoise_sym_state:mix_hash(SSD0, TestBin), + + ExpHash1 = enoise_crypto:hash(blake2b, <>), + ExpHash2 = h2b("0x8DC23DE176F6B3581FB7E18F258A47B1E1A8090BF55978868F1AC88C672DC3918FA4D1828338FB5DF652F5C33D57C79537CB5D074057EF59C346D0B35A160F71"), + ?assertMatch(ExpHash1, enoise_sym_state:h(SSE1)), + ?assertMatch(ExpHash2, enoise_sym_state:h(SSD1)), + + {ok, SSE2, TestBin} = enoise_sym_state:encrypt_and_hash(SSE1, TestBin), + {ok, SSD2, TestBin} = enoise_sym_state:decrypt_and_hash(SSD1, TestBin), + + SSE3 = enoise_sym_state:mix_key(SSE2, TestBin), + SSD3 = enoise_sym_state:mix_key(SSD2, TestBin), + + ExpEncrypt = h2b("0x24FB13758E6BA9901A4CEA117AE1D9AF757B02CAE96EFDFDA5ED3927BDD9FEA0239F7F673E924AAE81E6"), + {ok, SSE4, Encrypt} = enoise_sym_state:encrypt_and_hash(SSE3, TestBin), + ?assertMatch(ExpEncrypt, Encrypt), + {ok, SSD4, Decrypt} = enoise_sym_state:decrypt_and_hash(SSD3, ExpEncrypt), + ?assertMatch(TestBin, Decrypt), + + Key1 = h2b("0x893FD190EDB611D9AF73868C8AB020F7A13C62F70F7F74C46859CF4A1E71BB74"), + Key2 = h2b("0x492E210AD0181CE70BF9CE80308DE45EAE1FA76E1ACE22A829EF6F1A01C6E2C8"), + + {CSE1, CSE2} = enoise_sym_state:split(SSE4), + ?assertMatch(Key1, enoise_cipher_state:key(CSE1)), + ?assertMatch(Key2, enoise_cipher_state:key(CSE2)), + + {CSD1, CSD2} = enoise_sym_state:split(SSD4), + ?assertMatch(Key1, enoise_cipher_state:key(CSD1)), + ?assertMatch(Key2, enoise_cipher_state:key(CSD2)), + + ok. + +h2b(S) -> test_utils:hex_str_to_bin(S). diff --git a/test/test_utils.erl b/test/test_utils.erl index 36e429e..d5e7138 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -31,5 +31,68 @@ chacha_data() -> "a6ad5cb4022b02709b") , mac => hex_str_to_bin("0xeead9d67890cbb22392336fea1851f38") }. + +blake2b_data() -> + [#{ input => <<>>, + output => hex_str_to_bin("0x786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419" + "d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce") + }, + #{ input => <<"abc">>, + output => hex_str_to_bin("0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d1" + "7d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") + }, + #{ input => <<"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq">>, + output => hex_str_to_bin("0x7285ff3e8bd768d69be62b3bf18765a325917fa9744ac2f582a20850bc2b1141" + "ed1b3e4528595acc90772bdf2d37dc8a47130b44f33a02e8730e5ad8e166e888") + }, + #{ input => <<"abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn" + "hijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu">>, + output => hex_str_to_bin("0xce741ac5930fe346811175c5227bb7bfcd47f42612fae46c0809514f9e0e3a11" + "ee1773287147cdeaeedff50709aa716341fe65240f4ad6777d6bfaf9726e5e52") + }]. + +blake2s_data() -> + #{ input => <<"abc">>, + output => hex_str_to_bin("0x508C5E8C327C14E2E1A72BA34EEB452F37458B209ED63A294D999B4C86675982") + }. + +blake2b_hmac_data() -> + [#{ key => hex_str_to_bin("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), + data => hex_str_to_bin("0x6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666"), + hmac => hex_str_to_bin("0x4054489AA4225A07BD7F4C89330AA6412B612AADC8FA86AFBC8EC6AC2D0F3AC8" + "ECDB6601B060F47488D4074C562F848B9F6168BA8CDEE22E399057B5D53129C9") + }, + #{ key => hex_str_to_bin("0x4054489AA4225A07BD7F4C89330AA6412B612AADC8FA86AFBC8EC6AC2D0F3AC8" + "ECDB6601B060F47488D4074C562F848B9F6168BA8CDEE22E399057B5D53129C9"), + data => hex_str_to_bin("0x01"), + hmac => hex_str_to_bin("0x359D3AA619DF4F73E4E8EA31D05F5631C96F119D46F6BB44B5C7772B862747E7" + "818D4BC8907C1EBA90B06AD7925EC5E751E4E92D0E0233F893CD3FED8DD6FB76") + }, + #{ key => hex_str_to_bin("0x4054489AA4225A07BD7F4C89330AA6412B612AADC8FA86AFBC8EC6AC2D0F3AC8" + "ECDB6601B060F47488D4074C562F848B9F6168BA8CDEE22E399057B5D53129C9"), + data => hex_str_to_bin("0x359D3AA619DF4F73E4E8EA31D05F5631C96F119D46F6BB44B5C7772B862747E7" + "818D4BC8907C1EBA90B06AD7925EC5E751E4E92D0E0233F893CD3FED8DD6FB7602"), + hmac => hex_str_to_bin("0x37E23F26F8445E3B5A88949B98606131774BA4D15F2C6E17A0A43972BB4EB6B5" + "CBB42F57D8B1B63B4C9EA64B0493E82A6F6D3A7037C33212EF6E4F56E321D4D9") + }]. + +blake2b_hkdf_data() -> + [#{ key => hex_str_to_bin("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), + data => hex_str_to_bin("0x6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666" + "6666666666666666666666666666666666666666666666666666666666666666"), + out1 => hex_str_to_bin("0x359D3AA619DF4F73E4E8EA31D05F5631C96F119D46F6BB44B5C7772B862747E7" + "818D4BC8907C1EBA90B06AD7925EC5E751E4E92D0E0233F893CD3FED8DD6FB76"), + out2 => hex_str_to_bin("0x37E23F26F8445E3B5A88949B98606131774BA4D15F2C6E17A0A43972BB4EB6B5" + "CBB42F57D8B1B63B4C9EA64B0493E82A6F6D3A7037C33212EF6E4F56E321D4D9") + }]. + + hex_str_to_bin("0x" ++ Rest) -> << <<(list_to_integer([C], 16)):4>> || C <- Rest >>.