diff --git a/Makefile.am b/Makefile.am index f45633b8..f4b93625 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,8 @@ UTIL_FILES = \ coord/store.hpp \ crypto/arc4.cpp \ crypto/arc4.hpp \ + crypto/ice.cpp \ + crypto/ice.hpp \ crypto/tea.cpp \ crypto/tea.hpp \ crypto/xtea.cpp \ @@ -360,6 +362,7 @@ TEST_BIN = \ test/colour \ test/coord \ test/crypto/arc4 \ + test/crypto/ice \ test/crypto/tea \ test/crypto/xtea \ test/crypto/xxtea \ diff --git a/crypto/ice.cpp b/crypto/ice.cpp new file mode 100644 index 00000000..ff090e5f --- /dev/null +++ b/crypto/ice.cpp @@ -0,0 +1,437 @@ +/* + * 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. + * + * Copyright 2016 Danny Robson + */ + +// Derived from Mathew Kwan's 1996 C++ public domain implementation of ICE: +// http://www.darkside.com.au/ice/ +// +// M. Kwan, The Design of the ICE Encryption Algorithm, proceedings of Fast +// Software Encryption - Fourth International Workshop, Haifa, Israel, +// Springer-Verlag, pp. 69-82, 1997 + +#include "./ice.hpp" + +#include "endian.hpp" + +#include + + +/////////////////////////////////////////////////////////////////////////////// +/* + * C++ implementation of the ICE encryption algorithm. + * + * Written by Matthew Kwan - July 1996 + */ + + +/* The S-boxes */ +static uint32_t ice_sbox[4][1024]; +static bool ice_sboxes_initialised = false; + + +/* Modulo values for the S-boxes */ +static +constexpr +uint_fast16_t +ice_smod[4][4] = { + {333, 313, 505, 369}, + {379, 375, 319, 391}, + {361, 445, 451, 397}, + {397, 425, 395, 505} +}; + + +/* XOR values for the S-boxes */ +constexpr +uint8_t +ice_sxor[4][4] = { + {0x83, 0x85, 0x9b, 0xcd}, + {0xcc, 0xa7, 0xad, 0x41}, + {0x4b, 0x2e, 0xd4, 0x33}, + {0xea, 0xcb, 0x2e, 0x04} +}; + + +/* Permutation values for the P-box */ +constexpr +uint32_t +ice_pbox[32] = { + 0x00000001, 0x00000080, 0x00000400, 0x00002000, + 0x00080000, 0x00200000, 0x01000000, 0x40000000, + 0x00000008, 0x00000020, 0x00000100, 0x00004000, + 0x00010000, 0x00800000, 0x04000000, 0x20000000, + 0x00000004, 0x00000010, 0x00000200, 0x00008000, + 0x00020000, 0x00400000, 0x08000000, 0x10000000, + 0x00000002, 0x00000040, 0x00000800, 0x00001000, + 0x00040000, 0x00100000, 0x02000000, 0x80000000 +}; + + +/* The key rotation schedule */ +constexpr +std::array +ice_keyrot[2] = { + { 0, 1, 2, 3, 2, 1, 3, 0, }, + { 1, 3, 2, 0, 3, 1, 0, 2, }, +}; + + +/* + * 8-bit Galois Field multiplication of a by b, modulo m. + * Just like arithmetic multiplication, except that additions and + * subtractions are replaced by XOR. + */ + +template +static +T +gf_mult (T a, T b, const T m) +{ + T res = 0; + + while (b) { + if (b & 1u) + res ^= a; + + a <<= 1u; + b >>= 1u; + + if (a >= 256) + a ^= m; + } + + return res; +} + + +/* + * Galois Field exponentiation. + * Raise the base to the power of 7, modulo m. + */ + +template +static +T +gf_exp7 (const T b, + const T m) +{ + if (b == 0) + return 0; + + T x; + + x = gf_mult (b, b, m); + x = gf_mult (b, x, m); + x = gf_mult (x, x, m); + + return gf_mult (b, x, m); +} + + +/* + * Carry out the ICE 32-bit P-box permutation. + */ + +static +uint32_t +ice_perm32 (uint32_t x) +{ + uint32_t res = 0; + const uint32_t *pbox = ice_pbox; + + while (x) { + if (x & 1) + res |= *pbox; + pbox++; + x >>= 1; + } + + return res; +} + + +/* + * Initialise the ICE S-boxes. + * This only has to be done once. + */ +static +void +ice_sboxes_init (void) +{ + for (unsigned i = 0; i < 1024; i++) { + const uint_fast16_t col = (i >> 1) & 0xff; + const uint_fast16_t row = (i & 0x1) | ((i & 0x200) >> 8); + + for (unsigned j = 0; j < 4; ++j) { + const auto p = gf_exp7 ( + col ^ ice_sxor[j][row], + ice_smod[j][row] + ) << (24 - j * 8); + + ice_sbox[j][i] = ice_perm32 (p); + } + } +} + + +/* + * Create a new ICE key. + */ + +ice::ice (unsigned n, + const uint64_t *key_first, + const uint64_t *key_last) +{ + if (!ice_sboxes_initialised) { + ice_sboxes_init (); + ice_sboxes_initialised = true; + } + + if (n < 1) { + m_size = 1; + m_rounds = 8; + } else { + m_size = n; + m_rounds = n * 16; + } + + m_schedule.resize (m_rounds); + + set (key_first, key_last); +} + + +/* + * Destroy an ICE key. + */ + +ice::~ice () +{ + for (auto &s: m_schedule) + std::fill (std::begin (s), std::end (s), 0); + + m_rounds = m_size = 0; +} + + +/* + * The single round ICE f function. + */ + +static +uint32_t +ice_f (uint32_t p, const ice::subkey_t &sk) +{ + uint_fast64_t tl, tr; /* Expanded 40-bit values */ + uint_fast64_t al, ar; /* Salted expanded 40-bit values */ + + /* Left half expansion */ + tl = ((p >> 16) & 0x3ff) | (((p >> 14) | (p << 18)) & 0xffc00); + + /* Right half expansion */ + tr = (p & 0x3ff) | ((p << 2) & 0xffc00); + + /* Perform the salt permutation */ + // al = (tr & sk->val[2]) | (tl & ~sk->val[2]); + // ar = (tl & sk->val[2]) | (tr & ~sk->val[2]); + al = sk[2] & (tl ^ tr); + ar = al ^ tr; + al ^= tl; + + al ^= sk[0]; /* XOR with the subkey */ + ar ^= sk[1]; + + /* S-box lookup and permutation */ + return ( + ice_sbox[0][al >> 10] | ice_sbox[1][al & 0x3ff] + | ice_sbox[2][ar >> 10] | ice_sbox[3][ar & 0x3ff] + ); +} + + +/* + * Encrypt a block of 8 bytes of data with the given ICE key. + */ +uint64_t +ice::encrypt (const uint64_t _ptext) const +{ + union { + uint64_t pword; + uint8_t pbytes[8]; + }; + + pword = hton (_ptext); + + uint32_t l, r; + + l = (((uint32_t) pbytes[0]) << 24u) + | (((uint32_t) pbytes[1]) << 16u) + | (((uint32_t) pbytes[2]) << 8u) + | pbytes[3]; + r = (((uint32_t) pbytes[4]) << 24u) + | (((uint32_t) pbytes[5]) << 16u) + | (((uint32_t) pbytes[6]) << 8u) + | pbytes[7]; + + for (unsigned i = 0; i < m_rounds; i += 2) { + l ^= ice_f (r, m_schedule[i]); + r ^= ice_f (l, m_schedule[i + 1]); + } + + union { + uint64_t cword; + uint8_t cbytes[8]; + }; + + for (unsigned i = 0; i < 4; i++) { + cbytes[3 - i] = r & 0xff; + cbytes[7 - i] = l & 0xff; + + r >>= 8u; + l >>= 8u; + } + + return hton (cword); +} + + +/* + * Decrypt a block of 8 bytes of data with the given ICE key. + */ + +uint64_t +ice::decrypt (const uint64_t _ctext) const +{ + union { + uint64_t cword; + uint8_t cbytes[8]; + }; + + cword = hton (_ctext); + + uint32_t l, r; + + l = (((uint32_t) cbytes[0]) << 24u) + | (((uint32_t) cbytes[1]) << 16u) + | (((uint32_t) cbytes[2]) << 8u) + | cbytes[3]; + r = (((uint32_t) cbytes[4]) << 24u) + | (((uint32_t) cbytes[5]) << 16u) + | (((uint32_t) cbytes[6]) << 8u) + | cbytes[7]; + + for (int i = m_rounds - 1; i > 0; i -= 2) { + l ^= ice_f (r, m_schedule[i]); + r ^= ice_f (l, m_schedule[i - 1]); + } + + union { + uint64_t pword; + uint8_t pbytes[8]; + }; + + for (unsigned i = 0; i < 4; i++) { + pbytes[3 - i] = r & 0xff; + pbytes[7 - i] = l & 0xff; + + r >>= 8; + l >>= 8; + } + + return hton (pword); +} + + +/* + * Set 8 rounds [n, n+7] of the key schedule of an ICE key. + */ + +void +ice::scheduleBuild (std::array &kb, + int n, + const std::array &keyrot) +{ + for (unsigned i = 0; i < 8; i++) { + int kr = keyrot[i]; + subkey_t &isk = m_schedule[n + i]; + + std::fill (std::begin (isk), std::end (isk), 0); + + for (unsigned j = 0; j < 15; j++) { + uint32_t &curr_sk = isk[j % 3]; + + for (unsigned k = 0; k < 4; k++) { + auto &curr_kb = kb[(kr + k) & 3]; + unsigned bit = curr_kb & 1; + + curr_sk = (curr_sk << 1) | bit; + curr_kb = (curr_kb >> 1) | ((bit ^ 1) << 15); + } + } + } +} + + +/* + * Set the key schedule of an ICE key. + */ + +void +ice::set (const uint64_t *_key_first, const uint64_t *_key_last) +{ + auto key = reinterpret_cast (_key_first); + + if (m_rounds == 8) { + std::array kb; + + for (unsigned i = 0; i < 4; i++) + kb[3 - i] = (key[i * 2] << 8) | key[i * 2 + 1]; + + scheduleBuild (kb, 0, ice_keyrot[0]); + return; + } + + for (unsigned i = 0; i < m_size; i++) { + std::array kb; + + for (unsigned j = 0; j < 4; j++) + kb[3 - j] = (key[i * 8 + j * 2] << 8) | key[i * 8 + j * 2 + 1]; + + scheduleBuild (kb, i * 8, ice_keyrot[0]); + scheduleBuild (kb, m_rounds - 8 - i * 8, ice_keyrot[1]); + } +} + + +/* + * Return the key size, in bytes. + */ + +unsigned +ice::key_size () const +{ + return (m_size * 8); +} + + +/* + * Return the block size, in bytes. + */ + +unsigned +ice::block_size () const +{ + return (8); +} diff --git a/crypto/ice.hpp b/crypto/ice.hpp new file mode 100644 index 00000000..d0426d57 --- /dev/null +++ b/crypto/ice.hpp @@ -0,0 +1,64 @@ +/* + * 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. + * + * Copyright 2016 Danny Robson + */ + +#ifndef __CRYPTO_ICE_HPP +#define __CRYPTO_ICE_HPP + +#include +#include +#include + +// An implementation of the ICE symmetric-key block cipher +// +// we make a token attempt to zero our buffers, but we can't guarantee this +// will take place (given compiler optimisations). further security is +// outside the scope of this class. +class ice { + public: + ice (unsigned n, uint64_t key); + + ice (unsigned n, + const uint64_t *key_first, + const uint64_t *key_last); + ~ice (); + + void + set (const uint64_t *key_first, const uint64_t *key_last); + + uint64_t encrypt (uint64_t plaintext) const; + uint64_t decrypt (uint64_t ciphertext) const; + + unsigned key_size () const; + unsigned block_size () const; + + using subkey_t = std::array; + + private: + void + scheduleBuild (std::array &k, + int n, + const std::array &keyrot); + + unsigned m_size; + unsigned m_rounds; + + std::vector m_schedule; +}; + +struct ice_error : public std::runtime_error { using runtime_error::runtime_error; }; +struct level_error : public ice_error { using ice_error::ice_error; }; + +#endif diff --git a/test/crypto/ice.cpp b/test/crypto/ice.cpp new file mode 100644 index 00000000..0c6403e0 --- /dev/null +++ b/test/crypto/ice.cpp @@ -0,0 +1,43 @@ +#include "crypto/ice.hpp" +#include "tap.hpp" +#include "iterator.hpp" +#include "stream.hpp" +#include "endian.hpp" + +#include +#include +#include +#include + +int +main (int, char**) +{ + const uint64_t data = 0xfedcba9876543210; + + struct { + unsigned level; + uint64_t crypt; + std::vector key; + } TESTS[] = { + { 0, 0xde240d83a00a9cc0, { 0xdeadbeef01234567 } }, + { 1, 0x7d6ef1ef30d47a96, { 0xdeadbeef01234567 } }, + { 2, 0xf94840d86972f21c, { 0x0011223344556677, 0x8899aabbccddeeff } }, + }; + + util::TAP::logger tap; + + for (const auto &t: TESTS) { + std::vector k (t.key.cbegin (), t.key.cend ()); + std::transform (k.cbegin (), k.cend (), k.begin (), hton); + + ice key (t.level, k.data (), k.data () + k.size ()); + + auto crypt = key.encrypt (data); + auto plain = key.decrypt (t.crypt); + + tap.expect_eq (crypt, t.crypt, "ICE level %u certification, encrypt", t.level); + tap.expect_eq (plain, data, "ICE level %u certification, decrypt", t.level); + } + + return tap.status (); +}