Skip to content
This repository has been archived by the owner on Dec 10, 2019. It is now read-only.

Commit

Permalink
Add preliminary TCP support for AEAD #126
Browse files Browse the repository at this point in the history
  • Loading branch information
librehat committed Sep 17, 2017
1 parent 3531ad4 commit 2550b01
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 83 deletions.
58 changes: 38 additions & 20 deletions lib/cipher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,26 @@ typedef Botan::SecureVector<Botan::byte> SecureByteArray;
#define DataOfSecureByteArray(sba) sba.begin()
#endif

namespace {

// Copied from libsodium's sodium_increment
void nonceIncrement(unsigned char *n, const size_t nlen)
{
uint_fast16_t c = 1U;
for (size_t i = 0U; i < nlen; i++) {
c += static_cast<uint_fast16_t>(n[i]);
n[i] = static_cast<unsigned char>(c);
c >>= 8;
}
}

}

Cipher::Cipher(const std::string &method,
const std::string &psKey,
const std::string &sKey,
const std::string &iv,
bool encrypt,
QObject *parent) :
QObject(parent),
key(psKey),
bool encrypt) :
key(sKey),
iv(iv),
cipherInfo(cipherInfoMap.at(method))
{
Expand All @@ -70,20 +83,13 @@ Cipher::Cipher(const std::string &method,
}
#endif
try {
#ifdef USE_BOTAN2
if (cipherInfoMap.at(method).type == CipherType::AEAD) {
// Initialises necessary class members for AEAD ciphers
kdf.reset(new Botan::HKDF(new Botan::HMAC(new Botan::SHA_160())));
}
#endif

Botan::SymmetricKey _key(
reinterpret_cast<const Botan::byte *>(key.data()),
key.size());
Botan::InitializationVector _iv(
reinterpret_cast<const Botan::byte *>(iv.data()),
iv.size());
Botan::Keyed_Filter *filter = Botan::get_cipher(cipherInfo.internalName, _key, _iv,
filter = Botan::get_cipher(cipherInfo.internalName, _key, _iv,
encrypt ? Botan::ENCRYPTION : Botan::DECRYPTION);
// Botan::pipe will take control over filter
// we shouldn't deallocate filter externally
Expand All @@ -97,7 +103,7 @@ Cipher::~Cipher()
{
}

const std::map<std::string, Cipher::CipherInfo> Cipher::cipherInfoMap = {
const std::unordered_map<std::string, Cipher::CipherInfo> Cipher::cipherInfoMap = {
{"aes-128-cfb", {"AES-128/CFB", 16, 16, Cipher::CipherType::STREAM}},
{"aes-192-cfb", {"AES-192/CFB", 24, 16, Cipher::CipherType::STREAM}},
{"aes-256-cfb", {"AES-256/CFB", 32, 16, Cipher::CipherType::STREAM}},
Expand Down Expand Up @@ -137,6 +143,12 @@ std::string Cipher::update(const std::string &data)
pipe->process_msg(reinterpret_cast<const Botan::byte *>
(data.data()), data.size());
SecureByteArray c = pipe->read_all(Botan::Pipe::LAST_MESSAGE);
if (cipherInfo.type == CipherType::AEAD) {
nonceIncrement(reinterpret_cast<unsigned char*>(&iv[0]), iv.length());
filter->set_iv(Botan::InitializationVector(
reinterpret_cast<const Botan::byte *>(iv.data()), iv.size()
));
}
return std::string(reinterpret_cast<const char *>(DataOfSecureByteArray(c)),
c.size());
} else {
Expand All @@ -163,7 +175,12 @@ std::string Cipher::randomIv(int length)

std::string Cipher::randomIv(const std::string &method)
{
return randomIv(cipherInfoMap.at(method).ivLen);
CipherInfo cipherInfo = cipherInfoMap.at(method);
if (cipherInfo.type == AEAD) {
return std::string(cipherInfo.ivLen, static_cast<char>(0));
} else {
return randomIv(cipherInfo.ivLen);
}
}

std::string Cipher::hmacSha1(const std::string &key, const std::string &msg)
Expand Down Expand Up @@ -212,14 +229,15 @@ std::vector<std::string> Cipher::supportedMethods()

#ifdef USE_BOTAN2
/*
* Derives per-session subkey from the master key and IV, which is required
* Derives per-session subkey from the master key, which is required
* for Shadowsocks AEAD ciphers
*/
std::string Cipher::deriveSubkey() const
std::string Cipher::deriveAeadSubkey(size_t length, const std::string& masterKey, const std::string& salt)
{
Q_ASSERT(kdf);
std::string salt = randomIv(cipherInfo.saltLen);
SecureByteArray skey = kdf->derive_key(cipherInfo.keyLen, reinterpret_cast<const uint8_t*>(key.data()), key.length(), salt, kdfLabel);
std::unique_ptr<Botan::KDF> kdf;
kdf.reset(new Botan::HKDF(new Botan::HMAC(new Botan::SHA_160())));
//std::string salt = randomIv(cipherInfo.saltLen);
SecureByteArray skey = kdf->derive_key(length, reinterpret_cast<const uint8_t*>(masterKey.data()), masterKey.length(), salt, kdfLabel);
return std::string(reinterpret_cast<const char *>(DataOfSecureByteArray(skey)),
skey.size());
}
Expand Down
30 changes: 13 additions & 17 deletions lib/cipher.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,35 +30,32 @@
#define CIPHER_H

#include <array>
#include <map>
#include <unordered_map>
#include <memory>
#include <QObject>
#include "rc4.h"
#include "chacha.h"
#include "export.h"

namespace Botan {
class Keyed_Filter;
class Pipe;
class KDF;
class HashFunction;
class MessageAuthenticationCode;
}

namespace QSS {

class QSS_EXPORT Cipher : public QObject
class Cipher
{
Q_OBJECT
public:
/**
* @brief Cipher
* @param method The cipher method name (in Shadowsocks convention)
* @param psKey The pre-shared master key
* @param sKey The secret key (dervied per-session for AEAD)
* @param iv The initialiser vector, aka nonce
* @param encrypt Whether the operation is to encrypt, otherwise it's to decrypt
* @param parent The parent QObject pointer
*/
Cipher(const std::string &method, const std::string &psKey, const std::string &iv, bool encrypt, QObject *parent = 0);
Cipher(const std::string &method, const std::string &sKey, const std::string &iv, bool encrypt);
Cipher(Cipher &&) = default;
~Cipher();

Expand All @@ -82,7 +79,7 @@ class QSS_EXPORT Cipher : public QObject
/*
* The key of this map is the encryption method (shadowsocks convention)
*/
static const std::map<std::string, CipherInfo> cipherInfoMap;
static const std::unordered_map<std::string, CipherInfo> cipherInfoMap;

/*
* The label/info string used for key derivation function
Expand All @@ -100,6 +97,7 @@ class QSS_EXPORT Cipher : public QObject

/**
* @brief randomIv An overloaded function to generate randomised IV for given cipher method
* For AEAD ciphers, this method returns all zeros
* @param method The Shadowsocks cipher method name
* @return
*/
Expand All @@ -121,20 +119,18 @@ class QSS_EXPORT Cipher : public QObject
*/
static std::string hmacSha1(const std::string &key, const std::string &msg);

#ifdef USE_BOTAN2
static std::string deriveAeadSubkey(size_t length, const std::string &masterKey, const std::string& salt);
#endif

private:
Botan::Keyed_Filter *filter;
std::unique_ptr<Botan::Pipe> pipe;
std::unique_ptr<RC4> rc4;
std::unique_ptr<ChaCha> chacha;
const std::string key; // preshared key
const std::string iv; // nonce
std::string iv; // nonce
const CipherInfo cipherInfo;

#ifdef USE_BOTAN2
// AEAD support needs Botan-2 library
std::unique_ptr<Botan::KDF> kdf;

std::string deriveSubkey() const;
#endif
};

}
Expand Down
114 changes: 74 additions & 40 deletions lib/encryptor.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* encryptor.cpp - the source file of Encryptor class
*
* Copyright (C) 2014-2015 Symeon Huang <hzwhuang@gmail.com>
* Copyright (C) 2014-2017 Symeon Huang <hzwhuang@gmail.com>
*
* This file is part of the libQtShadowsocks.
*
Expand All @@ -22,17 +22,17 @@

#include "encryptor.h"
#include <QtEndian>
#include <QDebug>

using namespace QSS;

namespace {
std::string evpBytesToKey(const std::string& method, const std::string &password)
std::string evpBytesToKey(const Cipher::CipherInfo& cipherInfo, const std::string &password)
{
std::vector<std::string> m;
std::string data;
int i = 0;

auto cipherInfo = Cipher::cipherInfoMap.at(method);
while (m.size() < cipherInfo.keyLen + cipherInfo.ivLen) {
if (i == 0) {
data = password;
Expand All @@ -57,80 +57,114 @@ Encryptor::Encryptor(const std::string &method,
const std::string &password,
QObject *parent) :
QObject(parent),
cipherInfo(Cipher::cipherInfoMap.at(method)),
method(method),
password(evpBytesToKey(method, password)),
chunkId(0),
enCipher(nullptr),
deCipher(nullptr)
masterKey(evpBytesToKey(cipherInfo, password)),
chunkId(0)
{
enCipherIV = Cipher::randomIv(this->method);
}

void Encryptor::reset()
{
if (enCipher) {
enCipher->deleteLater();
enCipher = nullptr;
enCipherIV = Cipher::randomIv(this->method);
}
if (deCipher) {
deCipher->deleteLater();
deCipher = nullptr;
}
enCipher.reset();
deCipher.reset();
chunkId = 0;
}

Cipher* Encryptor::initEncipher()
{
salt = Cipher::randomIv(cipherInfo.saltLen);
const std::string iv = Cipher::randomIv(method);
const std::string key = cipherInfo.type == Cipher::CipherType::AEAD
? Cipher::deriveAeadSubkey(cipherInfo.keyLen, masterKey, salt) : masterKey;
Cipher* cipher = new Cipher(method, key, iv, true);
enCipherIV = iv;
return cipher;
}

Cipher* Encryptor::initDecipher(const std::string& in)
{
const std::string iv = cipherInfo.type == Cipher::CipherType::AEAD
? std::string(cipherInfo.ivLen, static_cast<char>(0)) : in.substr(0, cipherInfo.ivLen);
const std::string key = cipherInfo.type == Cipher::CipherType::AEAD
? Cipher::deriveAeadSubkey(cipherInfo.keyLen, masterKey, in.substr(0, cipherInfo.saltLen)) : masterKey;
Cipher* cipher = new Cipher(method, key, iv, false);
return cipher;
}

std::string Encryptor::encrypt(const std::string &in)
{
std::string out;
std::string header;
if (!enCipher) {
enCipher = new Cipher(method, password, enCipherIV, true, this);
out = enCipherIV + enCipher->update(in);
enCipher.reset(initEncipher());
header = cipherInfo.type == Cipher::CipherType::AEAD ? salt : enCipherIV;
}

std::string encrypted;
#ifdef USE_BOTAN2
if (cipherInfo.type == Cipher::CipherType::AEAD) {
uint16_t inLen = in.length() & 0x3FFF;
if (inLen != in.length()) {
incompleteChunk += in.substr(inLen);
}
std::string length(2, static_cast<char>(0));
qToBigEndian(inLen, reinterpret_cast<uint16_t*>(&length[0]));
std::string encLength = enCipher->update(length); // length + tag
std::string encPayload = enCipher->update(in.substr(0, inLen)); // payload + tag
encrypted = encLength + encPayload;
} else {
out = enCipher->update(in);
#endif
encrypted = enCipher->update(in);
#ifdef USE_BOTAN2
}
return out;
#endif
return header + encrypted;
}

std::string Encryptor::decrypt(const std::string &in)
std::string Encryptor::decrypt(std::string in)
{
std::string out;
if (!deCipher) {
int ivLen = Cipher::cipherInfoMap.at(method).ivLen;
std::string iv = in.substr(0, ivLen);
if (iv.size() != ivLen) {
return out;
deCipher.reset(initDecipher(in));
if (cipherInfo.type == Cipher::CipherType::AEAD) {
in = in.substr(cipherInfo.saltLen);
} else {
in = in.substr(cipherInfo.ivLen);
}
deCipher = new Cipher(method, password, iv, false, this);
out = deCipher->update(in.substr(ivLen));
}

#ifdef USE_BOTAN2
if (cipherInfo.type == Cipher::CipherType::AEAD) {
in = incompleteChunk + in;
std::string decLength = deCipher->update(in.substr(0, 2 + cipherInfo.tagLen));
uint16_t length = qFromBigEndian(*reinterpret_cast<const uint16_t*>(decLength.data()));
out = deCipher->update(in.substr(2 + cipherInfo.tagLen, length + cipherInfo.tagLen));
incompleteChunk = in.substr(2 + cipherInfo.tagLen + length + cipherInfo.tagLen);
} else {
#endif
out = deCipher->update(in);
#ifdef USE_BOTAN2
}
#endif
return out;
}

std::string Encryptor::encryptAll(const std::string &in)
{
if (enCipher) {
enCipher->deleteLater();
}
std::string iv = enCipherIV;
enCipherIV = Cipher::randomIv(method);
enCipher = new Cipher(method, password, iv, true, this);
enCipher.reset(new Cipher(method, masterKey, iv, true));
return iv + enCipher->update(in);
}

std::string Encryptor::decryptAll(const std::string &in)
{
if (deCipher) {
deCipher->deleteLater();
}
int ivLen = Cipher::cipherInfoMap.at(method).ivLen;
std::string iv = in.substr(0, ivLen);
if (iv.size() != ivLen) {
return std::string();
}
deCipher = new Cipher(method, password, iv, false, this);
deCipher.reset(new Cipher(method, masterKey, iv, false));
return deCipher->update(in.substr(Cipher::cipherInfoMap.at(method).ivLen));
}

Expand All @@ -145,13 +179,13 @@ std::string Encryptor::deCipherIV() const

void Encryptor::addHeaderAuth(std::string &headerData) const
{
std::string authCode = Cipher::hmacSha1(enCipherIV + password, headerData);
std::string authCode = Cipher::hmacSha1(enCipherIV + masterKey, headerData);
headerData.append(authCode);
}

void Encryptor::addHeaderAuth(std::string &data, const int &headerLen) const
{
std::string authCode = Cipher::hmacSha1(enCipherIV + password, data.substr(0, headerLen));
std::string authCode = Cipher::hmacSha1(enCipherIV + masterKey, data.substr(0, headerLen));
data.insert(headerLen, authCode.data(), authCode.size());
}

Expand All @@ -170,7 +204,7 @@ void Encryptor::addChunkAuth(std::string &data)

bool Encryptor::verifyHeaderAuth(const char *data, const int &headerLen) const
{
return Cipher::hmacSha1(deCipherIV() + password, std::string(data, headerLen)).compare(
return Cipher::hmacSha1(deCipherIV() + masterKey, std::string(data, headerLen)).compare(
std::string(data + headerLen, Cipher::AUTH_LEN)) == 0;
}

Expand Down
Loading

0 comments on commit 2550b01

Please sign in to comment.