You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2022/05/09 13:29:39 UTC
[couchdb] branch aegis_3.x updated: use AES_SIV (RFC 5297) instead of AES Key Wrap
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch aegis_3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git
The following commit(s) were added to refs/heads/aegis_3.x by this push:
new 3281159af use AES_SIV (RFC 5297) instead of AES Key Wrap
3281159af is described below
commit 3281159af84a62dd78d4514ee40be2423b08e7aa
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon May 9 14:29:05 2022 +0100
use AES_SIV (RFC 5297) instead of AES Key Wrap
---
rebar.config.script | 1 +
rel/reltool.config | 2 +
src/aegis/src/aegis.app.src | 28 ++++++++++
src/aegis/src/aegis.erl | 51 ++++++++++++++++++
src/aegis/src/aegis_cmac.erl | 67 +++++++++++++++++++++++
src/aegis/src/aegis_s2v.erl | 51 ++++++++++++++++++
src/aegis/src/aegis_siv.erl | 106 ++++++++++++++++++++++++++++++++++++
src/aegis/src/aegis_util.erl | 90 +++++++++++++++++++++++++++++++
src/couch/src/couch_file.erl | 10 ++--
src/couch/src/couch_keywrap.erl | 115 ----------------------------------------
10 files changed, 401 insertions(+), 120 deletions(-)
diff --git a/rebar.config.script b/rebar.config.script
index da3fc58a1..fdee29bc1 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -113,6 +113,7 @@ os:putenv("COUCHDB_APPS_CONFIG_DIR", filename:join([COUCHDB_ROOT, "rel/apps"])).
SubDirs = [
%% must be compiled first as it has a custom behavior
"src/couch_epi",
+ "src/aegis",
"src/couch_log",
"src/chttpd",
"src/couch",
diff --git a/rel/reltool.config b/rel/reltool.config
index ab26fb2ed..a7ab87c5f 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -26,6 +26,7 @@
syntax_tools,
xmerl,
%% couchdb
+ aegis,
b64url,
bear,
chttpd,
@@ -90,6 +91,7 @@
{app, xmerl, [{incl_cond, include}]},
%% couchdb
+ {app, aegis, [{incl_cond, include}]},
{app, b64url, [{incl_cond, include}]},
{app, bear, [{incl_cond, include}]},
{app, chttpd, [{incl_cond, include}]},
diff --git a/src/aegis/src/aegis.app.src b/src/aegis/src/aegis.app.src
new file mode 100644
index 000000000..9088ec46c
--- /dev/null
+++ b/src/aegis/src/aegis.app.src
@@ -0,0 +1,28 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+{application, aegis,
+ [{description, "An OTP application"},
+ {vsn, git},
+ {registered, []},
+ {applications,
+ [kernel,
+ stdlib,
+ crypto
+ ]},
+ {env,[]},
+ {modules, []},
+
+ {maintainers, []},
+ {licenses, []},
+ {links, []}
+ ]}.
diff --git a/src/aegis/src/aegis.erl b/src/aegis/src/aegis.erl
new file mode 100644
index 000000000..90e3f330a
--- /dev/null
+++ b/src/aegis/src/aegis.erl
@@ -0,0 +1,51 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis).
+
+-export([wrap_key/3, unwrap_key/3]).
+
+wrap_key(KEK, AAD, DEK) when is_binary(KEK), is_list(AAD), is_binary(DEK) ->
+ ExpandedKey = aegis_util:expand(KEK),
+ {CipherText, CipherTag} =
+ aegis_siv:block_encrypt(
+ ExpandedKey,
+ AAD,
+ DEK),
+ <<CipherTag/binary, CipherText/binary>>.
+
+
+unwrap_key(KEK, AAD, <<CipherTag:16/binary, CipherText/binary>>) when is_binary(KEK), is_list(AAD) ->
+ ExpandedKey = aegis_util:expand(KEK),
+ aegis_siv:block_decrypt(
+ ExpandedKey,
+ AAD,
+ {CipherText, CipherTag}).
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+aegis_test_() ->
+ [
+ ?_assertEqual(<<91,78,2,43,95,157,34,252,93,35,150,141,155,139,247,136,
+ 154,203,16,143,196,78,93,9,189,119,22,27,60,47,186,114,
+ 70,231,113,189,36,236,139,153,85,58,207,165,169,70,67,61>>,
+ wrap_key(<<0:256>>, [], <<1:256>>)),
+ ?_assertEqual(<<1:256>>,
+ unwrap_key(<<0:256>>, [],
+ <<91,78,2,43,95,157,34,252,93,35,150,141,155,139,247,136,
+ 154,203,16,143,196,78,93,9,189,119,22,27,60,47,186,114,
+ 70,231,113,189,36,236,139,153,85,58,207,165,169,70,67,61>>))
+ ].
+
+-endif.
diff --git a/src/aegis/src/aegis_cmac.erl b/src/aegis/src/aegis_cmac.erl
new file mode 100644
index 000000000..074a77346
--- /dev/null
+++ b/src/aegis/src/aegis_cmac.erl
@@ -0,0 +1,67 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_cmac).
+
+-export([cmac/2]).
+
+cmac(Key, Message) ->
+ cmac(Key, <<0:128>>, Message).
+
+cmac(Key, X, <<Last:16/binary>>) ->
+ {K1, _K2} = generate_subkeys(Key),
+ crypto:crypto_one_time(cmac_cipher(Key), Key, crypto:exor(X, crypto:exor(Last, K1)), true);
+
+cmac(Key, X, <<Block:16/binary, Rest/binary>>) ->
+ cmac(Key, crypto:crypto_one_time(cmac_cipher(Key), Key, crypto:exor(X, Block), true), Rest);
+
+cmac(Key, X, Last) ->
+ {_K1, K2} = generate_subkeys(Key),
+ crypto:crypto_one_time(cmac_cipher(Key), Key,
+ crypto:exor(X, crypto:exor(aegis_util:pad(Last), K2)), true).
+
+
+generate_subkeys(Key) ->
+ L = crypto:crypto_one_time(cmac_cipher(Key), Key, <<0:128>>, true),
+ K1 = aegis_util:double(L),
+ K2 = aegis_util:double(K1),
+ {K1, K2}.
+
+cmac_cipher(Key) when bit_size(Key) == 128 ->
+ aes_128_ecb;
+cmac_cipher(Key) when bit_size(Key) == 256 ->
+ aes_256_ecb.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+cmac_test_() ->
+ [
+ ?_assertEqual(<<16#bb1d6929e95937287fa37d129b756746:128>>,
+ cmac(<<16#2b7e151628aed2a6abf7158809cf4f3c:128>>,
+ <<>>)),
+
+ ?_assertEqual(<<16#070a16b46b4d4144f79bdd9dd04a287c:128>>,
+ cmac(<<16#2b7e151628aed2a6abf7158809cf4f3c:128>>,
+ <<16#6bc1bee22e409f96e93d7e117393172a:128>>)),
+
+ ?_assertEqual(<<16#028962f61b7bf89efc6b551f4667d983:128>>,
+ cmac(<<16#603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4:256>>,
+ <<>>)),
+
+ ?_assertEqual(<<16#28a7023f452e8f82bd4bf28d8c37c35c:128>>,
+ cmac(<<16#603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4:256>>,
+ <<16#6bc1bee22e409f96e93d7e117393172a:128>>))
+ ].
+
+-endif.
diff --git a/src/aegis/src/aegis_s2v.erl b/src/aegis/src/aegis_s2v.erl
new file mode 100644
index 000000000..0bae2c403
--- /dev/null
+++ b/src/aegis/src/aegis_s2v.erl
@@ -0,0 +1,51 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_s2v).
+
+-export([s2v/3]).
+
+
+s2v(Key, [], <<>>) ->
+ aegis_cmac:cmac(Key, <<1:128>>);
+
+s2v(Key, AAD, PlainText) when length(AAD) < 127 ->
+ s2v(Key, AAD, PlainText, aegis_cmac:cmac(Key, <<0:128>>)).
+
+s2v(Key, [], PlainText, Acc) when bit_size(PlainText) >= 128 ->
+ aegis_cmac:cmac(Key, aegis_util:xorend(PlainText, Acc));
+
+s2v(Key, [], PlainText, Acc) ->
+ aegis_cmac:cmac(Key,
+ crypto:exor(aegis_util:double(Acc), aegis_util:pad(PlainText)));
+
+s2v(Key, [H | T], PlainText, Acc0) ->
+ Acc1 = crypto:exor(aegis_util:double(Acc0), aegis_cmac:cmac(Key, H)),
+ s2v(Key, T, PlainText, Acc1).
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+s2v_0_test() ->
+ ?assertEqual(<<16#85632d07c6e8f37f950acd320a2ecc93:128>>,
+ s2v(
+ <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0:128>>,
+ [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+ <<16#112233445566778899aabbccddee:112>>)).
+
+%% for test coverage only. this value does not come from a test vector.
+s2v_1_test() ->
+ ?assertEqual(<<106,56,130,35,180,192,121,7,97,30,181,248,111,114,85,151>>,
+ s2v(<<0:128>>, [], <<>>)).
+
+
+-endif.
diff --git a/src/aegis/src/aegis_siv.erl b/src/aegis/src/aegis_siv.erl
new file mode 100644
index 000000000..4c0542956
--- /dev/null
+++ b/src/aegis/src/aegis_siv.erl
@@ -0,0 +1,106 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_siv).
+
+-export([block_encrypt/3, block_decrypt/3]).
+
+-spec block_encrypt(binary(), list(), binary()) -> {binary(), binary()}.
+block_encrypt(Key, AAD, PlainText)
+ when bit_size(Key) == 256; bit_size(Key) == 512 ->
+ {K1, K2} = split(Key),
+ <<V:128>> = aegis_s2v:s2v(K1, AAD, PlainText),
+ Q = V band 16#ffffffffffffffff7fffffff7fffffff,
+ CipherText = aes_ctr(K2, <<Q:128>>, PlainText),
+ {CipherText, <<V:128>>}.
+
+
+block_decrypt(Key, AAD, {CipherText, <<V:128>>})
+ when bit_size(Key) == 256; bit_size(Key) == 512 ->
+ {K1, K2} = split(Key),
+ Q = V band 16#ffffffffffffffff7fffffff7fffffff,
+ PlainText = aes_ctr(K2, <<Q:128>>, CipherText),
+ <<T:128>> = aegis_s2v:s2v(K1, AAD, PlainText),
+ case V == T of
+ true ->
+ PlainText;
+ false ->
+ fail
+ end.
+
+
+split(Key) ->
+ Half = byte_size(Key) div 2,
+ <<K1:Half/binary, K2:Half/binary>> = Key,
+ {K1, K2}.
+
+
+aes_ctr(Key, IV, Data) ->
+ Cipher = ctr_cipher(Key),
+ crypto:crypto_one_time(Cipher, Key, IV, Data, true).
+
+
+ctr_cipher(Key) when bit_size(Key) == 128 ->
+ aes_128_ctr;
+
+ctr_cipher(Key) when bit_size(Key) == 256 ->
+ aes_256_ctr.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+encrypt_test_() ->
+ [
+ ?_assertEqual({<<16#40c02b9690c4dc04daef7f6afe5c:112>>,
+ <<16#85632d07c6e8f37f950acd320a2ecc93:128>>},
+ block_encrypt(
+ <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+ [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+ <<16#112233445566778899aabbccddee:112>>)),
+
+ ?_assertEqual({<<16#cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d:376>>,
+ <<16#7bdb6e3b432667eb06f4d14bff2fbd0f:128>>},
+ block_encrypt(
+ <<16#7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f:256>>,
+ [<<16#00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100:320>>,
+ <<16#102030405060708090a0:80>>,
+ <<16#09f911029d74e35bd84156c5635688c0:128>>],
+ <<16#7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553:376>>))
+ ].
+
+decrypt_test_() ->
+ [
+ ?_assertEqual(<<16#112233445566778899aabbccddee:112>>,
+ block_decrypt(
+ <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+ [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+ {<<16#40c02b9690c4dc04daef7f6afe5c:112>>, <<16#85632d07c6e8f37f950acd320a2ecc93:128>>})),
+
+ ?_assertEqual(fail,
+ block_decrypt(
+ <<16#fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff:256>>,
+ [<<16#101112131415161718191a1b1c1d1e1f2021222324252627:192>>],
+ {<<16#40c02b9690c4dc04daef7f6afe5c:112>>, <<16#85632d07c6e8f37f950acd320a2ecc94:128>>})),
+
+ ?_assertEqual(<<16#7468697320697320736f6d6520706c61696e7465787420746f20656e6372797074207573696e67205349562d414553:376>>,
+
+ block_decrypt(
+ <<16#7f7e7d7c7b7a79787776757473727170404142434445464748494a4b4c4d4e4f:256>>,
+ [<<16#00112233445566778899aabbccddeeffdeaddadadeaddadaffeeddccbbaa99887766554433221100:320>>,
+ <<16#102030405060708090a0:80>>,
+ <<16#09f911029d74e35bd84156c5635688c0:128>>],
+ {<<16#cb900f2fddbe404326601965c889bf17dba77ceb094fa663b7a3f748ba8af829ea64ad544a272e9c485b62a3fd5c0d:376>>,
+ <<16#7bdb6e3b432667eb06f4d14bff2fbd0f:128>>}))
+ ].
+
+-endif.
diff --git a/src/aegis/src/aegis_util.erl b/src/aegis/src/aegis_util.erl
new file mode 100644
index 000000000..1705ac222
--- /dev/null
+++ b/src/aegis/src/aegis_util.erl
@@ -0,0 +1,90 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(aegis_util).
+
+-export([
+ double/1,
+ expand/1,
+ pad/1,
+ xorend/2
+]).
+
+%% @doc double
+%% is the multiplication of S and 0...010 in the finite field
+%% represented using the primitive polynomial
+%% x<sup>128</sup> + x<sup>7</sup> + x<sup>2</sup> + x + 1.
+%% @end
+-spec double(Val :: binary()) -> binary().
+double(<<0:1, Lo:127>>) ->
+ <<(Lo bsl 1):128>>;
+
+double(<<1:1, Lo:127>>) ->
+ crypto:exor(<<(Lo bsl 1):128>>, <<16#87:128>>).
+
+
+%% because SIV only uses half the bits of the input key
+%% to encrypt and the other half for the authentication/IV
+%% we expand our keys to 512 to ensure an overall security
+%% threshold of 256.
+expand(Key) when bit_size(Key) == 256 ->
+ %% expansion technique from Bjoern Tackmann - IBM Zurich
+ K0 = crypto:crypto_one_time(aes_256_ecb, Key, <<0:128>>, true),
+ K1 = crypto:crypto_one_time(aes_256_ecb, Key, <<1:128>>, true),
+ K2 = crypto:crypto_one_time(aes_256_ecb, Key, <<2:128>>, true),
+ K3 = crypto:crypto_one_time(aes_256_ecb, Key, <<3:128>>, true),
+ <<K0/binary, K1/binary, K2/binary, K3/binary>>.
+
+
+%% @doc pad
+%% indicates padding of string X, len(X) < 128, out to 128 bits by
+%% the concatenation of a single bit of 1 followed by as many 0 bits
+%% as are necessary.
+%% @end
+-spec pad(binary()) -> binary().
+pad(Val) when bit_size(Val) =< 128 ->
+ Pad = 128 - bit_size(Val) - 1,
+ <<Val/binary, 1:1, 0:Pad>>.
+
+
+%% @doc xorend
+%% where len(A) >= len(B), means xoring a string B onto the end of
+%% string A -- i.e., leftmost(A, len(A)-len(B)) || (rightmost(A,
+%% len(B)) xor B).
+%% @end
+-spec xorend(binary(), binary()) -> binary().
+xorend(A, B) when byte_size(A) >= byte_size(B) ->
+ Diff = byte_size(A) - byte_size(B),
+ <<Left:Diff/binary, Right/binary>> = A,
+ Xor = crypto:exor(Right, B),
+ <<Left/binary, Xor/binary>>.
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+double_0_test() ->
+ ?assertEqual(
+ <<16#1c09bf5f83df7e080280b050b37e0e74:128>>,
+ double(<<16#0e04dfafc1efbf040140582859bf073a:128>>)).
+
+double_1_test() ->
+ ?assertEqual(
+ <<16#dbe13bd0ed8c85dc9af179c99ddbf819:128>>,
+ double(<<16#edf09de876c642ee4d78bce4ceedfc4f:128>>)).
+
+pad_test() ->
+ ?assertEqual(
+ <<16#112233445566778899aabbccddee8000:128>>,
+ pad(<<16#112233445566778899aabbccddee:112>>)).
+
+-endif.
diff --git a/src/couch/src/couch_file.erl b/src/couch/src/couch_file.erl
index f64c36ef2..892d0d2b6 100644
--- a/src/couch/src/couch_file.erl
+++ b/src/couch/src/couch_file.erl
@@ -23,7 +23,7 @@
-define(IS_OLD_STATE(S), is_pid(S#file.db_monitor)).
-define(PREFIX_SIZE, 5).
-define(DEFAULT_READ_COUNT, 1024).
--define(ENCRYPTED_HEADER, 0,1,2,3,4,5,6,7).
+-define(ENCRYPTED_HEADER, 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15).
-type block_id() :: non_neg_integer().
-type location() :: non_neg_integer().
@@ -931,7 +931,7 @@ reset_eof(#file{} = File) ->
%% we've wiped all the data, including the wrapped key, so we need a new one.
init_crypto(#file{eof = 0} = File) ->
Key = crypto:strong_rand_bytes(32),
- WrappedKey = couch_keywrap:key_wrap(master_key(), Key),
+ WrappedKey = aegis:wrap_key(master_key(), [], Key),
Header = <<?ENCRYPTED_HEADER, WrappedKey/binary>>,
ok = file:write(File#file.fd, Header),
ok = file:sync(File#file.fd),
@@ -939,9 +939,9 @@ init_crypto(#file{eof = 0} = File) ->
%% we're opening an existing file and need to unwrap the key.
init_crypto(#file{enc = undefined, dec = undefined} = File) ->
- case file:pread(File#file.fd, 0, 48) of
- {ok, <<?ENCRYPTED_HEADER, WrappedKey/binary>>} ->
- case couch_keywrap:key_unwrap(master_key(), WrappedKey) of
+ case file:pread(File#file.fd, 0, 64) of
+ {ok, <<?ENCRYPTED_HEADER, WrappedKey:48/binary>>} ->
+ case aegis:unwrap_key(master_key(), [], WrappedKey) of
fail ->
{error, unwrap_failed};
Key when is_binary(Key) ->
diff --git a/src/couch/src/couch_keywrap.erl b/src/couch/src/couch_keywrap.erl
deleted file mode 100644
index 2cfa2b104..000000000
--- a/src/couch/src/couch_keywrap.erl
+++ /dev/null
@@ -1,115 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-% http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_keywrap).
-
-%% Implementation of NIST Special Publication 800-38F
-%% For wrapping and unwrapping keys with AES.
-
--export([key_wrap/2, key_unwrap/2]).
-
--define(ICV1, 16#A6A6A6A6A6A6A6A6).
-
--spec key_wrap(WrappingKey :: binary(), KeyToWrap :: binary()) -> binary().
-key_wrap(WrappingKey, KeyToWrap) when
- is_binary(WrappingKey), bit_size(KeyToWrap) rem 64 == 0
-->
- N = bit_size(KeyToWrap) div 64,
- wrap(WrappingKey, <<?ICV1:64>>, KeyToWrap, 1, 6 * N).
-
-wrap(_WrappingKey, A, R, T, End) when T > End ->
- <<A/binary, R/binary>>;
-wrap(WrappingKey, A, R, T, End) ->
- <<R1:64, Rest/binary>> = R,
- <<MSB_B:64, LSB_B:64>> = crypto:crypto_one_time(aes_256_ecb, WrappingKey, <<A/binary, R1:64>>, true),
- wrap(WrappingKey, <<(MSB_B bxor T):64>>, <<Rest/binary, LSB_B:64>>, T + 1, End).
-
--spec key_unwrap(WrappingKey :: binary(), KeyToUnwrap :: binary()) -> binary() | fail.
-key_unwrap(WrappingKey, KeyToUnwrap) when
- is_binary(WrappingKey), bit_size(KeyToUnwrap) rem 64 == 0
-->
- N = (bit_size(KeyToUnwrap) div 64),
- <<A:64, R/binary>> = KeyToUnwrap,
- case unwrap(WrappingKey, <<A:64>>, R, 6 * (N - 1)) of
- <<?ICV1:64, UnwrappedKey/binary>> ->
- UnwrappedKey;
- _ ->
- fail
- end.
-
-unwrap(_WrappingKey, A, R, 0) ->
- <<A/binary, R/binary>>;
-unwrap(WrappingKey, <<A:64>>, R, T) ->
- RestSize = bit_size(R) - 64,
- <<Rest:RestSize, R2:64>> = R,
- <<MSB_B:64, LSB_B:64>> = crypto:crypto_one_time(aes_256_ecb, WrappingKey, <<(A bxor T):64, R2:64>>, false),
- unwrap(WrappingKey, <<MSB_B:64>>, <<LSB_B:64, Rest:RestSize>>, T - 1).
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
-
-wrap_test_() ->
- [
- %% 128 KEK / 128 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F:128>>,
- <<16#00112233445566778899AABBCCDDEEFF:128>>,
- <<16#1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5:192>>
- ),
- %% 192 KEK / 128 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F1011121314151617:192>>,
- <<16#00112233445566778899AABBCCDDEEFF:128>>,
- <<16#96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D:192>>
- ),
- %% 256 KEK / 128 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
- <<16#00112233445566778899AABBCCDDEEFF:128>>,
- <<16#64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7:192>>
- ),
- %% 192 KEK / 192 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F1011121314151617:192>>,
- <<16#00112233445566778899AABBCCDDEEFF0001020304050607:192>>,
- <<16#031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2:256>>
- ),
- %% 256 KEK / 192 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
- <<16#00112233445566778899AABBCCDDEEFF0001020304050607:192>>,
- <<16#A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1:256>>
- ),
- %% 256 KEK / 256 DATA
- test_wrap_unwrap(
- <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
- <<16#00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F:256>>,
- <<
- 16#28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21:320
- >>
- )
- ].
-
-test_wrap_unwrap(WrappingKey, KeyToWrap, ExpectedWrappedKey) ->
- [
- ?_assertEqual(ExpectedWrappedKey, key_wrap(WrappingKey, KeyToWrap)),
- ?_assertEqual(KeyToWrap, key_unwrap(WrappingKey, key_wrap(WrappingKey, KeyToWrap)))
- ].
-
-fail_test() ->
- KEK = <<16#000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F:256>>,
- CipherText = <<
- 16#28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD20:320
- >>,
- ?assertEqual(fail, key_unwrap(KEK, CipherText)).
-
--endif.