forked from tink-crypto/tink
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a version of AES-EAX using boringssl as underlying
library. Things not done: Things to check: - The code does not use EVP. There are plans to just add code in assembly. Hence this is kind of backup code for the case where AES instructions are not available. Hence it is unclear whether using that horrible EVP interface makes sense. - depot/google3/security/util/crypt/ contains a version of AES_EAX using EVP. The main problem with this implementation is that it is not thread safe. - The code needs unaligned big-endian load and store. I have not been able to find a function that achieves this efficiently. Compilers can sometimes (but not always) optimize the load or stores. PiperOrigin-RevId: 201177778 GitOrigin-RevId: 2601a0d6eb42804747718f283761774922cdf8f5
- Loading branch information
Showing
3 changed files
with
681 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
// Copyright 2017 Google Inc. | ||
// | ||
// 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. | ||
// | ||
/////////////////////////////////////////////////////////////////////////////// | ||
|
||
#include "tink/subtle/aes_eax_boringssl.h" | ||
|
||
#include <string> | ||
#include <vector> | ||
#include <memory> | ||
|
||
#include "tink/aead.h" | ||
#include "tink/subtle/random.h" | ||
#include "tink/util/errors.h" | ||
#include "tink/util/status.h" | ||
#include "tink/util/statusor.h" | ||
#include "openssl/err.h" | ||
#include "openssl/evp.h" | ||
|
||
namespace crypto { | ||
namespace tink { | ||
namespace subtle { | ||
|
||
static const int BLOCK_SIZE = 16; | ||
|
||
namespace { | ||
// TODO(bleichen): There has to be a way to implement | ||
// the following routines fast. E.g. Clang 6.0.0 optimizes | ||
// Load64, Store64, BigendianLoad64, but does not optimize | ||
// BigEndianStore64. | ||
|
||
// Loads and stores 8 bytes. The endianness of the two routines | ||
// does not matter, as long as the two routines use the same order. | ||
uint64_t Load64(const uint8_t src[8]) { | ||
uint64_t res; | ||
memmove(&res, src, 8); | ||
return res; | ||
} | ||
|
||
void Store64(uint8_t dst[8], uint64_t val) { | ||
memmove(dst, &val, 8); | ||
} | ||
|
||
uint64_t BigEndianLoad64(const uint8_t src[8]) { | ||
return static_cast<uint64_t>(src[7]) | ||
| (static_cast<uint64_t>(src[6]) << 8) | ||
| (static_cast<uint64_t>(src[5]) << 16) | ||
| (static_cast<uint64_t>(src[4]) << 24) | ||
| (static_cast<uint64_t>(src[3]) << 32) | ||
| (static_cast<uint64_t>(src[2]) << 40) | ||
| (static_cast<uint64_t>(src[1]) << 48) | ||
| (static_cast<uint64_t>(src[0]) << 56); | ||
} | ||
|
||
void BigEndianStore64(uint8_t dst[8], uint64_t val) { | ||
dst[0] = (val >> 56) & 0xff; | ||
dst[1] = (val >> 48) & 0xff; | ||
dst[2] = (val >> 40) & 0xff; | ||
dst[3] = (val >> 32) & 0xff; | ||
dst[4] = (val >> 24) & 0xff; | ||
dst[5] = (val >> 16) & 0xff; | ||
dst[6] = (val >> 8) & 0xff; | ||
dst[7] = val & 0xff; | ||
} | ||
|
||
void XorBlock(const uint8_t x[BLOCK_SIZE], | ||
const uint8_t y[BLOCK_SIZE], | ||
uint8_t res[BLOCK_SIZE]) { | ||
uint64_t r0 = Load64(x) ^ Load64(y); | ||
uint64_t r1 = Load64(x + 8) ^ Load64(y + 8); | ||
Store64(res, r0); | ||
Store64(res + 8, r1); | ||
} | ||
|
||
void MultiplyByX(const uint8_t in[BLOCK_SIZE], | ||
uint8_t out[BLOCK_SIZE]) { | ||
uint64_t in_high = BigEndianLoad64(in); | ||
uint64_t in_low = BigEndianLoad64(in + 8); | ||
uint64_t out_high = (in_high << 1) ^ (in_low >> 63); | ||
// If the most significant bit is set then the result has to | ||
// be reduced by x^128 + x^7 + x^4 + x^2 + x + 1. | ||
// The representation of x^7 + x^4 + x^2 + x + 1 is 0x87. | ||
uint64_t out_low = (in_low << 1) ^ (in_high >> 63 ? 0x87 : 0); | ||
BigEndianStore64(out, out_high); | ||
BigEndianStore64(out + 8, out_low); | ||
} | ||
|
||
bool EqualBlocks(const uint8_t x[BLOCK_SIZE], | ||
const uint8_t y[BLOCK_SIZE]) { | ||
uint64_t res = Load64(x) ^ Load64(y); | ||
res |= Load64(x + 8) ^ Load64(y + 8); | ||
return res == 0; | ||
} | ||
|
||
} // namespace | ||
|
||
bool AesEaxBoringSsl::IsValidKeySize(size_t key_size_in_bytes) { | ||
return key_size_in_bytes == 16 || | ||
key_size_in_bytes == 32; | ||
} | ||
|
||
bool AesEaxBoringSsl::IsValidNonceSize(size_t nonce_size_in_bytes) { | ||
return nonce_size_in_bytes == 12 || | ||
nonce_size_in_bytes == 16; | ||
} | ||
|
||
AesEaxBoringSsl::AesEaxBoringSsl( | ||
absl::string_view key_value, size_t nonce_size) | ||
: nonce_size_(nonce_size) { | ||
int status = AES_set_encrypt_key( | ||
reinterpret_cast<const uint8_t*>(key_value.data()), key_value.size() * 8, | ||
&aeskey_); | ||
// status != 0 happens if key_value or aeskey_ is invalid. In both cases | ||
// this indicates a programming error. | ||
if (status != 0) { | ||
is_initialized_ = false; | ||
return; | ||
} | ||
uint8_t block[BLOCK_SIZE]; | ||
memset(block, 0, BLOCK_SIZE); | ||
EncryptBlock(block, block); | ||
MultiplyByX(block, B_); | ||
MultiplyByX(B_, P_); | ||
is_initialized_ = true; | ||
} | ||
|
||
crypto::tink::util::StatusOr<std::unique_ptr<Aead>> AesEaxBoringSsl::New( | ||
absl::string_view key_value, | ||
size_t nonce_size_in_bytes) { | ||
if (!IsValidKeySize(key_value.size())) { | ||
return util::Status(util::error::INTERNAL, "Invalid key"); | ||
} | ||
std::unique_ptr<AesEaxBoringSsl> aead( | ||
new AesEaxBoringSsl(key_value, nonce_size_in_bytes)); | ||
if (!aead->is_initialized_) { | ||
return util::Status(util::error::INTERNAL, | ||
"Could not initialize AesEaxBoringSsl"); | ||
} | ||
return std::unique_ptr<Aead>(aead.release()); | ||
} | ||
|
||
void AesEaxBoringSsl::Pad(const uint8_t* data, int len, | ||
uint8_t padded_block[BLOCK_SIZE]) const { | ||
// TODO(bleichen): What are we using in tink to encode assertions? | ||
// The caller must ensure that data is no longer than a block. | ||
// CHECK(0 <= len && len <= BLOCK_SIZE) << "Invalid data size"; | ||
memset(padded_block, 0, BLOCK_SIZE); | ||
memmove(padded_block, data, len); | ||
if (len == BLOCK_SIZE) { | ||
XorBlock(padded_block, B_, padded_block); | ||
} else { | ||
padded_block[len] = 0x80; | ||
XorBlock(padded_block, P_, padded_block); | ||
} | ||
} | ||
|
||
void AesEaxBoringSsl::EncryptBlock(const uint8_t in[BLOCK_SIZE], | ||
uint8_t out[BLOCK_SIZE]) const { | ||
AES_encrypt(in, out, &aeskey_); | ||
} | ||
|
||
void AesEaxBoringSsl::Omac( | ||
absl::string_view blob, | ||
int tag, | ||
uint8_t mac[BLOCK_SIZE]) const { | ||
Omac(reinterpret_cast<const uint8_t *>(blob.data()), blob.size(), tag, mac); | ||
} | ||
|
||
void AesEaxBoringSsl::Omac(const uint8_t* data, | ||
size_t len, | ||
int tag, | ||
uint8_t mac[BLOCK_SIZE]) const { | ||
uint8_t block[BLOCK_SIZE]; | ||
memset(block, 0, BLOCK_SIZE); | ||
block[15] = tag; | ||
if (len == 0) { | ||
XorBlock(block, B_, block); | ||
EncryptBlock(block, mac); | ||
return; | ||
} | ||
EncryptBlock(block, block); | ||
int idx = 0; | ||
while (len - idx > BLOCK_SIZE) { | ||
XorBlock(block, &data[idx], block); | ||
EncryptBlock(block, block); | ||
idx += BLOCK_SIZE; | ||
} | ||
uint8_t padded_block[BLOCK_SIZE]; | ||
Pad(&data[idx], len - idx, padded_block); | ||
XorBlock(block, padded_block, block); | ||
EncryptBlock(block, mac); | ||
} | ||
|
||
void AesEaxBoringSsl::CtrCrypt( | ||
const uint8_t N[BLOCK_SIZE], | ||
const uint8_t *in, | ||
uint8_t *result, | ||
size_t size) const { | ||
// This special case is necessary to avoid problems when in == null. | ||
// in == null is possible since absl::string_view can contain null pointers. | ||
if (size == 0) { | ||
return; | ||
} | ||
// Make a copy of N, since BoringSsl changes ctr. | ||
uint8_t ctr[BLOCK_SIZE]; | ||
memcpy(ctr, N, BLOCK_SIZE); | ||
unsigned int num = 0; | ||
uint8_t ecount_buf[BLOCK_SIZE]; | ||
memset(ecount_buf, 0, BLOCK_SIZE); | ||
AES_ctr128_encrypt(in, result, size, &aeskey_, ctr, ecount_buf, &num); | ||
} | ||
|
||
crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Encrypt( | ||
absl::string_view plaintext, | ||
absl::string_view additional_data) const { | ||
size_t ciphertext_size = plaintext.size() + nonce_size_ + TAG_SIZE; | ||
std::string ciphertext(ciphertext_size, '\0'); | ||
uint8_t N[BLOCK_SIZE]; | ||
const std::string nonce = Random::GetRandomBytes(nonce_size_); | ||
Omac(nonce, 0, N); | ||
uint8_t H[BLOCK_SIZE]; | ||
Omac(additional_data, 1, H); | ||
uint8_t* ct_start = reinterpret_cast<uint8_t*>(&ciphertext[nonce_size_]); | ||
CtrCrypt(N, reinterpret_cast<const uint8_t*>(plaintext.data()), | ||
ct_start, plaintext.size()); | ||
uint8_t mac[BLOCK_SIZE]; | ||
Omac(ct_start, plaintext.size(), 2, mac); | ||
XorBlock(mac, N, mac); | ||
XorBlock(mac, H, mac); | ||
memmove(&ciphertext[0], nonce.data(), nonce_size_); | ||
memmove(&ciphertext[ciphertext_size - TAG_SIZE], mac, TAG_SIZE); | ||
return std::move(ciphertext); | ||
} | ||
|
||
crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Decrypt( | ||
absl::string_view ciphertext, | ||
absl::string_view additional_data) const { | ||
size_t ct_size = ciphertext.size(); | ||
if (ct_size < nonce_size_ + TAG_SIZE) { | ||
return util::Status(util::error::INTERNAL, "Ciphertext too short"); | ||
} | ||
size_t out_size = ct_size - TAG_SIZE - nonce_size_; | ||
absl::string_view nonce = ciphertext.substr(0, nonce_size_); | ||
absl::string_view encrypted = ciphertext.substr(nonce_size_, out_size); | ||
absl::string_view tag = ciphertext.substr(ct_size - TAG_SIZE, TAG_SIZE); | ||
uint8_t N[BLOCK_SIZE]; | ||
Omac(nonce, 0, N); | ||
uint8_t H[BLOCK_SIZE]; | ||
Omac(additional_data, 1, H); | ||
uint8_t mac[BLOCK_SIZE]; | ||
Omac(encrypted, 2, mac); | ||
XorBlock(mac, N, mac); | ||
XorBlock(mac, H, mac); | ||
const uint8_t *sig = reinterpret_cast<const uint8_t*>(tag.data()); | ||
if (!EqualBlocks(mac, sig)) { | ||
return util::Status(util::error::INTERNAL, "Tag mismatch"); | ||
} | ||
std::string res(out_size, '\0'); | ||
CtrCrypt(N, reinterpret_cast<const uint8_t*>(encrypted.data()), | ||
reinterpret_cast<uint8_t*>(&res[0]), out_size); | ||
return std::move(res); | ||
} | ||
|
||
} // namespace subtle | ||
} // namespace tink | ||
} // namespace crypto | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Copyright 2017 Google Inc. | ||
// | ||
// 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. | ||
// | ||
/////////////////////////////////////////////////////////////////////////////// | ||
|
||
#ifndef TINK_SUBTLE_AES_EAX_BORINGSSL_H_ | ||
#define TINK_SUBTLE_AES_EAX_BORINGSSL_H_ | ||
|
||
#include <memory> | ||
|
||
#include "absl/strings/string_view.h" | ||
#include "tink/aead.h" | ||
#include "tink/util/status.h" | ||
#include "tink/util/statusor.h" | ||
#include "openssl/aes.h" | ||
#include "openssl/evp.h" | ||
|
||
namespace crypto { | ||
namespace tink { | ||
namespace subtle { | ||
|
||
class AesEaxBoringSsl : public Aead { | ||
public: | ||
// Constructs a new Aead cipher for Aes-EAX. | ||
// Currently supported key sizes are 128 and 256 bits. | ||
// Currently supported nonce sizes are 12 an 16 bytes. | ||
// The tag size is fixed to 16 bytes. | ||
static crypto::tink::util::StatusOr<std::unique_ptr<Aead>> New( | ||
absl::string_view key_value, size_t nonce_size_in_bytes); | ||
|
||
crypto::tink::util::StatusOr<std::string> Encrypt( | ||
absl::string_view plaintext, | ||
absl::string_view additional_data) const override; | ||
|
||
crypto::tink::util::StatusOr<std::string> Decrypt( | ||
absl::string_view ciphertext, | ||
absl::string_view additional_data) const override; | ||
|
||
virtual ~AesEaxBoringSsl() {} | ||
|
||
private: | ||
static const int TAG_SIZE = 16; | ||
static const int BLOCK_SIZE = 16; | ||
|
||
AesEaxBoringSsl() = delete; | ||
AesEaxBoringSsl(absl::string_view key_value, size_t nonce_size); | ||
|
||
// Returns whether key_size_in_bytes is a supported key size. | ||
static bool IsValidKeySize(size_t key_size_in_bytes); | ||
|
||
// Returns whether nonce_size_in_bytes is a supported size for the nonce. | ||
static bool IsValidNonceSize(size_t nonce_size_in_bytes); | ||
|
||
// Encrypts a single block with AES. | ||
void EncryptBlock(const uint8 in[BLOCK_SIZE], uint8 out[BLOCK_SIZE]) const; | ||
|
||
// Pads a partial data block of size 0 <= len <= BLOCK_SIZE. | ||
void Pad(const uint8* data, int len, uint8 padded_block[BLOCK_SIZE]) const; | ||
|
||
// Computes a Omac over blob. | ||
// tag is either 0, 1 or 2, depending over which value (nonce, aad, message) | ||
// the Omac is computed. | ||
// mac is the return value of the function. | ||
void Omac( | ||
absl::string_view blob, | ||
int tag, | ||
uint8 mac[BLOCK_SIZE]) const; | ||
|
||
// This is the same function as above with the difference that the blob | ||
// is represented by a pointer and its length. | ||
void Omac(const uint8* data, size_t len, int tag, uint8 mac[BLOCK_SIZE]) | ||
const; | ||
|
||
// Encrypts or decrypts some data using CTR mode. N are 16 bytes, which | ||
// are the result of an OMAC computation over the nonce. | ||
// in are the bytes that are encrypted or decrypted. result is the | ||
// encrypted rsp. decrypted value. size determines the size of in and result. | ||
void CtrCrypt( | ||
const uint8 N[BLOCK_SIZE], | ||
const uint8 *in, | ||
uint8 *result, | ||
size_t size) const; | ||
|
||
// TODO(bleichen): This class is immutable. But it seems difficult to | ||
// declare these members const, because the constructor is not trivial. | ||
AES_KEY aeskey_; | ||
uint8 B_[BLOCK_SIZE]; | ||
uint8 P_[BLOCK_SIZE]; | ||
const std::string key_; | ||
const size_t nonce_size_; | ||
// Set by the constructor to true if the initialization was successful. | ||
// New() is the only method that needs to check is_initialized_, since | ||
// New() will never return an AesEaxBoringssl instance that is not | ||
// initialized. | ||
bool is_initialized_; | ||
}; | ||
|
||
} // namespace subtle | ||
} // namespace tink | ||
} // namespace crypto | ||
|
||
#endif // TINK_SUBTLE_AES_EAX_BORINGSSL_H_ |
Oops, something went wrong.