summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Newson <rnewson@apache.org>2020-03-25 17:08:35 +0000
committerRobert Newson <rnewson@apache.org>2020-03-26 13:33:42 +0000
commit9912d49b63fa948a42ef505d8055ce290878077b (patch)
treea6dffc76201ac9810d849625022d99610a05efd8
parentd67f5073f935fd971924c8093e2e4b722dfc3439 (diff)
downloadcouchdb-9912d49b63fa948a42ef505d8055ce290878077b.tar.gz
Implement AES KW algorithm*couch-keywrap
For use by the native couchdb at-rest encryption feature. * From NIST Special Publication 800-38F.
-rw-r--r--src/couch/src/couch_keywrap.erl103
1 files changed, 103 insertions, 0 deletions
diff --git a/src/couch/src/couch_keywrap.erl b/src/couch/src/couch_keywrap.erl
new file mode 100644
index 000000000..0d1e3f59d
--- /dev/null
+++ b/src/couch/src/couch_keywrap.erl
@@ -0,0 +1,103 @@
+-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).
+
+%% Assume old crypto api
+-define(aes_ecb_encrypt(Key, Data),
+ crypto:block_encrypt(aes_ecb, Key, Data)).
+-define(aes_ecb_decrypt(Key, Data),
+ crypto:block_decrypt(aes_ecb, Key, Data)).
+
+%% Replace macros if new crypto api is available
+-ifdef(OTP_RELEASE).
+-if(?OTP_RELEASE >= 22).
+-define(key_alg(Key), case bit_size(Key) of 128 -> aes_128_ecb; 192 -> aes_192_ecb; 256 -> aes_256_ecb end).
+-undef(aes_ecb_encrypt).
+-define(aes_ecb_encrypt(Key, Data),
+ crypto:crypto_one_time(?key_alg(Key), Key, Data, true)).
+-undef(aes_ecb_decrypt).
+-define(aes_ecb_decrypt(Key, Data),
+ crypto:crypto_one_time(?key_alg(Key), Key, Data, false)).
+-endif.
+-endif.
+
+-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>> = ?aes_ecb_encrypt(WrappingKey, <<A/binary, R1:64>>),
+ 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>> = ?aes_ecb_decrypt(WrappingKey, <<(A bxor T):64, R2:64>>),
+ 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.