Skip to content
This repository has been archived by the owner on Apr 17, 2024. It is now read-only.

Commit

Permalink
C++ version of AES-SIV.
Browse files Browse the repository at this point in the history
The implementation uses old OpenSSL interfaces for AES.
I haven't found a good way to use newer ones.
Possibly, it is easier to just rewrite the code with intrinsics.

PiperOrigin-RevId: 212435613
GitOrigin-RevId: 6176b392469957303d3ebb34af794d422f5f34df
  • Loading branch information
bleichen authored and chuckx committed Sep 11, 2018
1 parent e6ac043 commit f048e88
Show file tree
Hide file tree
Showing 4 changed files with 649 additions and 0 deletions.
61 changes: 61 additions & 0 deletions cc/daead.h
@@ -0,0 +1,61 @@
// 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_DAEAD_H_
#define TINK_DAEAD_H_

#include "absl/strings/string_view.h"
#include "tink/util/statusor.h"

namespace crypto {
namespace tink {

///////////////////////////////////////////////////////////////////////////////
// The interface for deterministic authenticated encryption with associated
// data.
// TODO(bleichen): Copy the interface from Java.
// Check the properties:
// - authenticated
// - secure in multi-user setting
// - thread safe/copy safe
// References:
// https://eprint.iacr.org/2016/1124.pdf
class Daead {
public:
// Encrypts 'plaintext' with 'associated_data' as associated data
// deterministically, and returns the resulting ciphertext.
// The ciphertext allows for checking authenticity and integrity
// of the associated data, but does not guarantee its secrecy.
virtual crypto::tink::util::StatusOr<std::string> EncryptDeterministically(
absl::string_view plaintext,
absl::string_view associated_data) const = 0;

// Decrypts 'ciphertext' with 'associated_data' as associated data,
// and returns the resulting plaintext.
// The decryption verifies the authenticity and integrity
// of the associated data, but there are no guarantees wrt. secrecy
// of that data.
virtual crypto::tink::util::StatusOr<std::string> DecryptDeterministically(
absl::string_view ciphertext,
absl::string_view associated_data) const = 0;

virtual ~Daead() {}
};

} // namespace tink
} // namespace crypto

#endif // TINK_DAEAD_H_
233 changes: 233 additions & 0 deletions cc/subtle/aes_siv_boringssl.cc
@@ -0,0 +1,233 @@
// 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_siv_boringssl.h"

#include <string>
#include <vector>

#include "tink/daead.h"
#include "tink/util/errors.h"
#include "tink/util/status.h"
#include "tink/util/statusor.h"
#include "openssl/err.h"
#include "openssl/aes.h"

namespace crypto {
namespace tink {
namespace subtle {

static void XorBlock(
const uint8_t x[16],
const uint8_t y[16],
uint8_t res[16]) {
for (int i = 0; i < 16; i++) {
res[i] = x[i] ^ y[i];
}
}

// static
crypto::tink::util::StatusOr<std::unique_ptr<Daead>> AesSivBoringSsl::New(
absl::string_view key_value) {
std::unique_ptr<AesSivBoringSsl> aes_siv(new AesSivBoringSsl());
if (aes_siv->SetKey(key_value)) {
return std::unique_ptr<Daead>(aes_siv.release());
} else {
return util::Status(util::error::INTERNAL, "invalid key size");
}
}

bool AesSivBoringSsl::SetKey(absl::string_view key) {
size_t key_size = key.size();
if (!IsValidKeySizeInBytes(key_size)) {
return false;
}
if (0 != AES_set_encrypt_key(
reinterpret_cast<const uint8_t*>(key.data()),
4 * key_size, &k1_)) {
return false;
}
if (0 != AES_set_encrypt_key(
reinterpret_cast<const uint8_t*>(key.data() + key_size / 2),
4 * key_size, &k2_)) {
return false;
}
uint8_t block[BLOCK_SIZE];
memset(block, 0, BLOCK_SIZE);
EncryptBlock(block, block);
MultiplyByX(block);
memcpy(cmac_k1_, block, BLOCK_SIZE);
MultiplyByX(block);
memcpy(cmac_k2_, block, BLOCK_SIZE);
return true;
}

void AesSivBoringSsl::CtrCrypt(const uint8_t siv[BLOCK_SIZE],
const uint8_t *in, uint8_t *out,
size_t size) const {
uint8_t iv[BLOCK_SIZE];
memcpy(iv, siv, BLOCK_SIZE);
iv[8] &= 0x7f;
iv[12] &= 0x7f;
unsigned int num = 0;
uint8_t ecount_buf[BLOCK_SIZE];
memset(ecount_buf, 0, BLOCK_SIZE);
AES_ctr128_encrypt(in, out, size, &k2_, iv, ecount_buf, &num);
}

void AesSivBoringSsl::EncryptBlock(const uint8_t in[BLOCK_SIZE],
uint8_t out[BLOCK_SIZE]) const {
AES_encrypt(in, out, &k1_);
}

// static
void AesSivBoringSsl::MultiplyByX(uint8_t block[BLOCK_SIZE]) {
uint8_t carry = block[0] >> 7;
for (size_t i = 0; i < BLOCK_SIZE - 1; i++) {
block[i] = (block[i] << 1) | (block[i+1] >> 7);
}
block[BLOCK_SIZE - 1 ] = (block[BLOCK_SIZE - 1] << 1) ^ (carry ? 0x87 : 0);
}

void AesSivBoringSsl::Cmac(const uint8_t* data, size_t size,
uint8_t mac[BLOCK_SIZE]) const {
size_t blocks = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
if (blocks == 0) {
blocks = 1;
}
size_t last_block_size = size - BLOCK_SIZE * (blocks - 1);
uint8_t block[BLOCK_SIZE];
memset(block, 0, BLOCK_SIZE);
size_t idx = 0;
for (size_t i = 0; i < blocks - 1; i++) {
XorBlock(block, &data[idx], block);
EncryptBlock(block, block);
idx += BLOCK_SIZE;
}
for (size_t j = 0; j < last_block_size; j++) {
block[j] ^= data[idx + j];
}
if (last_block_size == BLOCK_SIZE) {
XorBlock(block, cmac_k1_, block);
} else {
block[last_block_size] ^= 0x80;
XorBlock(block, cmac_k2_, block);
}
EncryptBlock(block, mac);
}

// Computes Cmac(XorEnd(data, last))
void AesSivBoringSsl::CmacLong(
const uint8_t* data, size_t size, const uint8_t last[BLOCK_SIZE],
uint8_t mac[BLOCK_SIZE]) const {
uint8_t block[BLOCK_SIZE];
memcpy(block, data, BLOCK_SIZE);
size_t idx = BLOCK_SIZE;
while (BLOCK_SIZE <= size - idx) {
EncryptBlock(block, block);
XorBlock(block, &data[idx], block);
idx += BLOCK_SIZE;
}
size_t remaining = size - idx;
for (int j = 0; j < BLOCK_SIZE - remaining; ++j) {
block[remaining + j] ^= last[j];
}
if (remaining == 0) {
XorBlock(block, cmac_k1_, block);
} else {
EncryptBlock(block, block);
for (int j = 0; j < remaining; ++j) {
block[j] ^= last[BLOCK_SIZE - remaining + j];
block[j] ^= data[idx + j];
}
block[remaining] ^= 0x80;
XorBlock(block, cmac_k2_, block);
}
EncryptBlock(block, mac);
}

void AesSivBoringSsl::S2v(const uint8_t* aad, size_t aad_size,
const uint8_t* msg, size_t msg_size,
uint8_t siv[BLOCK_SIZE]) const {
// This stuff could be precomputed.
uint8_t block[BLOCK_SIZE];
memset(block, 0, BLOCK_SIZE);
Cmac(block, BLOCK_SIZE, block);
MultiplyByX(block);

uint8_t aad_mac[BLOCK_SIZE];
Cmac(aad, aad_size, aad_mac);
XorBlock(block, aad_mac, block);

if (msg_size >= BLOCK_SIZE) {
CmacLong(msg, msg_size, block, siv);
} else {
MultiplyByX(block);
for (size_t i = 0; i < msg_size; i++) {
block[i] ^= msg[i];
}
block[msg_size] ^= 0x80;
Cmac(block, BLOCK_SIZE, siv);
}
}

util::StatusOr<std::string> AesSivBoringSsl::EncryptDeterministically(
absl::string_view plaintext,
absl::string_view additional_data) const {
uint8_t siv[BLOCK_SIZE];
S2v(reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size(),
reinterpret_cast<const uint8_t*>(plaintext.data()),
plaintext.size(),
siv);
size_t ciphertext_size = plaintext.size() + BLOCK_SIZE;
std::vector<uint8_t> ct(ciphertext_size);
memcpy(ct.data(), siv, BLOCK_SIZE);
CtrCrypt(siv, reinterpret_cast<const uint8_t*>(plaintext.data()),
ct.data() + BLOCK_SIZE, plaintext.size());
return std::string(reinterpret_cast<const char*>(ct.data()), ciphertext_size);
}

util::StatusOr<std::string> AesSivBoringSsl::DecryptDeterministically(
absl::string_view ciphertext,
absl::string_view additional_data) const {
if (ciphertext.size() < BLOCK_SIZE) {
return util::Status(util::error::INVALID_ARGUMENT, "ciphertext too short");
}
size_t plaintext_size = ciphertext.size() - BLOCK_SIZE;
std::vector<uint8_t> pt(plaintext_size);
const uint8_t *siv = reinterpret_cast<const uint8_t*>(&ciphertext[0]);
const uint8_t *ct = reinterpret_cast<const uint8_t*>(&ciphertext[BLOCK_SIZE]);
CtrCrypt(siv, ct, pt.data(), plaintext_size);

uint8_t s2v[BLOCK_SIZE];
S2v(reinterpret_cast<const uint8_t*>(additional_data.data()),
additional_data.size(), pt.data(), plaintext_size, s2v);
// Compare the siv from the ciphertext with the recomputed siv
uint8_t diff = 0;
for (int i = 0; i < BLOCK_SIZE; ++i) {
diff |= siv[i] ^ s2v[i];
}
if (diff != 0) {
return util::Status(util::error::INVALID_ARGUMENT, "invalid ciphertext");
}
return std::string(reinterpret_cast<const char*>(pt.data()), plaintext_size);
}

} // namespace subtle
} // namespace tink
} // namespace crypto

0 comments on commit f048e88

Please sign in to comment.