【问题标题】:Generic constant time compare function c++通用常数时间比较函数c ++
【发布时间】:2017-11-29 03:32:03
【问题描述】:

我正在编写一个 ProtectedPtr 类,该类使用 Windows Crypto API 保护内存中的对象,但在创建通用常量时间比较函数时遇到了问题。我当前的代码:

template <class T>
bool operator==(volatile const ProtectedPtr& other)
{
    std::size_t thisDataSize = sizeof(*protectedData) / sizeof(T);
    std::size_t otherDataSize = sizeof(*other) / sizeof(T);

    volatile auto thisData = (byte*)getEncyptedData();
    volatile auto otherData = (byte*)other.getEncyptedData();

    if (thisDataSize != otherDataSize)
        return false;

    volatile int result = 0;
    for (int i = 0; i < thisDataSize; i++)
        result |= thisData[i] ^ otherData[i];

    return result == 0;
}

getEncryptedData 函数:

std::unique_ptr<T> protectedData;
const T& getEncyptedData() const
{
    ProtectMemory(true);
    return *protectedData;
}

问题是转换为字节*。将此类与字符串一起使用时,我的编译器抱怨无法将字符串转换为字节指针。我在想也许试图将我的函数基于 Go 的 ConstantTimeByteEq 函数,但它仍然让我回到了将模板类型转换为 int 或我可以执行二进制操作的东西的原始问题。

Go 的 ConstantTimeByteEq 函数:

func ConstantTimeByteEq(x, y uint8) int {
    z := ^(x ^ y)
    z &= z >> 4
    z &= z >> 2
    z &= z >> 1

    return int(z)
}

如何轻松地将模板类型转换为可以轻松执行二进制操作的东西?

更新根据 lockcmpxchg8b 的建议使用通用常量比较函数:

//only works on primative types, and types that don't have
//internal pointers pointing to dynamically allocated data
byte* serialize()
{
    const size_t size = sizeof(*protectedData);
    byte* out = new byte[size];

    ProtectMemory(false);
    memcpy(out, &(*protectedData), size);
    ProtectMemory(true);

    return out;
}

bool operator==(ProtectedPtr& other)
{
    if (sizeof(*protectedData) != sizeof(*other))
        return false;

    volatile auto thisData = serialize();
    volatile auto otherData = other.serialize();

    volatile int result = 0;
    for (int i = 0; i < sizeof(*protectedData); i++)
        result |= thisData[i] ^ otherData[i];

    //wipe the unencrypted copies of the data
    SecureZeroMemory(thisData, sizeof(thisData));
    SecureZeroMemory(otherData, sizeof(otherData));

    return result == 0;
}

【问题讨论】:

  • 为什么将其转换为字节并进行比较?为什么不依赖 T 的比较运算符?
  • (byte*)&amp;getEncryptedData()?
  • @sajas 他需要保证恒定时间比较。他不能保证默认比较器不会因第一个差异而退出。
  • @lockcmpxchg8b 抱歉,我没有解释那个函数。 ProtectedPtr::getEncryptedData() 在使用 CryptProtectMemory 在内存中加密后返回 ProtectedPtr 指向的内容。
  • @lockcmpxchg8b 编辑添加功能

标签: c++ templates generics cryptography constant-time


【解决方案1】:

通常,您要在当前代码中完成的任务称为Format Preserving Encryption。即,加密std::string,使得生成的密文也是有效的std::string。这比让加密过程从原始类型转换为平面字节数组要困难得多。

要转换为平面数组,请为“Serializer”对象声明第二个模板参数,该对象知道如何将 T 类型的对象序列化为无符号字符数组。您可以将其默认为适用于所有原始类型的通用 sizeof/memcpy 序列化程序。

这是std::string 的示例。

template <class T>
class Serializer
{
  public:
    virtual size_t serializedSize(const T& obj) const = 0;
    virtual size_t serialize(const T& obj, unsigned char *out, size_t max) const = 0;
    virtual void deserialize(const unsigned char *in, size_t len, T& out) const = 0;
};

class StringSerializer : public Serializer<std::string>
{
public:

  size_t serializedSize(const std::string& obj) const {
    return obj.length();
  };

  size_t serialize(const std::string& obj, unsigned char *out, size_t max) const {
    if(max >= obj.length()){
      memcpy(out, obj.c_str(), obj.length());
      return obj.length();
    }
    throw std::runtime_error("overflow");
  }

  void deserialize(const unsigned char *in, size_t len, std::string& out) const {
    out = std::string((const char *)in, (const char *)(in+len));
  }
};

一旦您将对象缩减为unsigned chars 的平面数组,那么您给定的恒定时间比较算法就可以正常工作。

这是使用上述序列化程序的示例代码的真正简化版本。

template <class T, class S>
class Test
{
  std::unique_ptr<unsigned char[]> protectedData;
  size_t serSize;
public:
  Test(const T& obj) : protectedData() {
    S serializer;
    size_t size = serializer.serializedSize(obj);

    protectedData.reset(new unsigned char[size]);
    serSize = serializer.serialize(obj, protectedData.get(), size);

    // "Encrypt"
    for(size_t i=0; i< size; i++)
      protectedData.get()[i] ^= 0xa5;
  }

  size_t getEncryptedLen() const {
    return serSize;
  }
  const unsigned char *getEncryptedData() const {
    return protectedData.get();
  }

  const T getPlaintextData() const {
    S serializer;
    T target;

    //"Decrypt"
    for(size_t i=0; i< serSize; i++)
      protectedData.get()[i] ^= 0xa5;

    serializer.deserialize(protectedData.get(), serSize, target);
    return target;
  }
};

int main(int argc, char *argv[])
{
  std::string data = "test";
  Test<std::string, StringSerializer> tester(data);

  const unsigned char *ptr = tester.getEncryptedData();
  std::cout << "\"Encrypted\" bytes: ";
  for(size_t i=0; i<tester.getEncryptedLen(); i++)
    std::cout << std::setw(2) << std::hex << std::setfill('0') << (unsigned int)ptr[i] << " ";
  std::cout << std::endl;

  std::string recov = tester.getPlaintextData();

  std::cout << "Recovered: " << recov << std::endl;
}

输出:

$ ./a.out
"Encrypted" bytes: d1 c0 d6 d1
Recovered: test

编辑:回答对原始/平面类型的通用序列化程序的请求。将此视为伪代码,因为我将其输入浏览器而无需测试。我不确定这是否是正确的模板语法。

template<class T>
class PrimitiveSerializer : public Serializer<T>
{
public:

  size_t serializedSize(const T& obj) const {
    return sizeof obj;
  };

  size_t serialize(const T& obj, unsigned char *out, size_t max) const {
    if(max >= sizeof obj){
      memcpy(out, &obj, sizeof obj);
      return sizeof obj;
    }
    throw std::runtime_error("overflow");
  }

  void deserialize(const unsigned char *in, size_t len, T& out) const {
    if(len < sizeof out) {
      throw std::runtime_error("underflow");
    }
    memcpy(&out, in, sizeof out);
  }
};

【讨论】:

  • 那么您是说要以这种方式解决问题,我需要为与 ProtectedPtr 一起使用的每个不同对象创建消毒器吗?我还需要创建自己的 FPE,而不是使用我一直在使用的 Windows API 函数?
  • 一个或另一个。您要么必须使用适合您的数据类型的 FPE,要么必须能够将该数据扁平化为字节数组。请注意,它是每个对象的 type 一个序列化程序,而不是每个对象。我几乎写了一个示例“通用序列化程序”,它只接受sizeof T,并执行memcpy(dest_out, &amp;obj, sizeof T)...,它适用于任何没有内部结构的对象(即,指向动态分配的缓冲区的内部指针)。跨度>
  • 抱歉,我就是这个意思,每种类型的对象一个序列化程序。好的,我之前实际上曾想过使用 memcpy,但不知道如何使用。通用序列化器会是什么样子?
  • 我添加了一个草图。
  • 非常感谢!您的草图效果很好,感谢您为我指明了正确的方向! (双关语)
【解决方案2】:

我很好奇编译器会给你什么错误。

也就是说,尝试转换为 const char*const void*

另一个问题可能是从 64 位指针转换为 8 位 byte。尝试投射到 intlonglonglong

编辑:根据您的反馈,另一个小改动:

volatile auto thisData = (byte*)&getEncyptedData();
volatile auto otherData = (byte*)&other.getEncyptedData();

(注意与号)。这将允许以前的演员表工作

【讨论】:

  • 这是一条评论
  • 我尝试转换为以上所有内容,编译器给出了诸如'type cast'之类的错误:无法从'const std::string'转换为'__int64'和'const std::string &ProtectedPtr<:string>::getEncyptedData(void) const':无法将 'this' 指针从 'volatile const ProtectedPtr<:string>' 转换为 'const ProtectedPtr<:string> &'(这些错误是特别是在尝试转换为 long long 时)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 2021-11-04
相关资源
最近更新 更多