C ++ Crypto: Phần 1- Hash
Tìm kiếm xung quanh các thư viện Crypto hiện đại.
Không thể tìm thấy bất cứ điều gì tốt.
Tôi biết có lẽ tôi đã làm điều này sai hoàn toàn vì vậy hãy làm việc với tôi ở đây. Sẽ có bốn đánh giá khác nhau cho bốn cấu trúc xây dựng dựa trên nhau:
- Băm
- Khoá băm
- Khóa mật khẩu
- Phản ứng thử thách muối
Đây là mã băm và cung cấp một trình bao bọc đơn giản xung quanh SHA-1 và SHA-256 nhưng mẫu đủ đơn giản để chúng ta có thể mở rộng nó cho các cơ chế băm khác.
Cấu trúc dữ liệu và cách triển khai được trình bày trong những câu hỏi này dựa trên RFC2104 và bài đăng này trên codeproject .
Ví dụ sử dụng:
DigestStore<Sha1> hash; // <- destination of hash
Sha1 hasher;
hasher.hash("This string can be hashsed", hash);
hash.h
#ifndef THORS_ANVIL_CRYPTO_HASH_H
#define THORS_ANVIL_CRYPTO_HASH_H
#ifdef __APPLE__
#define COMMON_DIGEST_FOR_OPENSSL
#include <CommonCrypto/CommonDigest.h>
#define THOR_SHA1(data, len, dst) CC_SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst) CC_SHA256(data, len, dst)
#else
#include <openssl/sha.h>
#define THOR_SHA1(data, len, dst) SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst) SHA256(data, len, dst)
#endif
#include <string>
#include <array>
//
// Wrapper for sha1 and sha256 hashing algorithms
//
// Provides a simple wrapper class with the appropriates types and size
// for the resulting "digest" object. Also provides several type safe
// versions of the hashing algorithm to allow multiple know types to
// be safely hashed.
namespace ThorsAnvil::Crypto
{
using Byte = char unsigned;
using DigestPtr = Byte*;
template<typename Hash>
using Digest = typename Hash::DigestStore;
template<std::size_t size>
class DigestStore
{
std::array<Byte, size> data;
public:
using iterator = typename std::array<Byte, size>::iterator;
operator Digest() {return &data[0];}
std::string_view view() {return std::string_view(reinterpret_cast<char const*>(&data[0]), std::size(data));}
Byte& operator[](std::size_t i) {return data[i];}
iterator begin() {return std::begin(data);}
iterator end() {return std::end(data);}
};
// These versions of the hashing function are good for hashing short
// amounts of text. Use these for passwords and validation hashes
// do not use them for hashing large documents.
struct Sha1
{
static constexpr std::size_t digestSize = SHA_DIGEST_LENGTH;
using DigestStore = DigestStore<SHA_DIGEST_LENGTH>;
void hash(DigestStore& src, DigestStore& dst) {THOR_SHA1(src, SHA_DIGEST_LENGTH, dst);}
void hash(std::string_view src, DigestStore& dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
void hash(std::string const& src, DigestStore& dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
// Use only if you know the destination is large enough!!
void hashUnsafe(std::string_view src, DigestPtr dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};
struct Sha256
{
static constexpr std::size_t digestSize = SHA256_DIGEST_LENGTH;
using DigestStore = DigestStore<SHA256_DIGEST_LENGTH>;
void hash(DigestStore& src, DigestStore& dst) {THOR_SHA256(src, SHA256_DIGEST_LENGTH, dst);}
void hash(std::string_view src, DigestStore& dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
void hash(std::string const& src, DigestStore& dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
// Use only if you know the destination is large enough!
void hashUnsafe(std::string_view src, Digestptr dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};
}
#endif
Trả lời
Viết hoa đúng SHA1
Tên của thuật toán là SHA1, không phải Sha1, vì vậy tôi nghĩ tốt hơn là sử dụng tất cả các chữ hoa ở đây. Điều đó làm cho việc ghi mã cho một thuật toán cụ thể dễ dàng hơn.
Bạn chỉ cần một lớp cho mỗi thuật toán băm
Thật vậy, như bạn đã đề cập trong câu trả lời của riêng mình, Sha1lớp này có vẻ thừa, vì nó không lưu trữ bất kỳ trạng thái nào. Tuy nhiên, thay vì tạo các hàm tĩnh bên trong một Sha1không gian tên, bạn có thể tạo các hàm đó thành các hàm thành viên của lớp giữ trạng thái thực tế. Điều này tránh lặp lại kiểu; ví dụ:
Sha1::DigestStore digest;
Sha1::hash("Bob", digest)
Trở thành:
Sha1::DigestStore digest;
digest.hash("Bob");
Sha1::Digest vs. Digest<Sha1>
Tôi nghĩ rằng có một không gian tên Sha1với một DigestStorevà các chức năng bên trong nó là một lựa chọn tồi. Bạn có thể làm nhiều việc hơn với SHA1 ngoài việc chỉ tạo các hàm băm đơn giản, chẳng hạn như bạn có thể muốn tạo một HMAC thay vì một hàm băm đơn giản. Vì vậy, bạn sẽ phải thêm các hàm để tạo HMAC cho mỗi không gian tên triển khai thuật toán băm. Sẽ tốt hơn nhiều nếu có các lớp Digestvà các lớp HMACđó được tạo mẫu trên thuật toán băm.
Cho phép các mã băm được cập nhật
Mã bạn đã viết chỉ thực hiện chuyển đổi một lần của một số đầu vào thành hàm băm. Tuy nhiên, không có gì lạ khi các chương trình không có tất cả dữ liệu mà chúng muốn tạo băm trong một vùng bộ nhớ liền kề, duy nhất. Trong những trường hợp đó, bạn muốn viết:
std::ostream output;
Digest<SHA1> digest;
digest.add("Header");
digest.add("Data");
digest.add("Footer");
output << digest.view();
Một số thuật toán thông báo có thể yêu cầu bạn gọi một số hàm để tính toán giá trị băm cuối cùng sau khi thêm tất cả dữ liệu. Bạn có thể thêm một finish()hàm rõ ràng hoặc gọi hàm này một cách ngầm định khi truy cập kết quả thông báo.
Nhận kết quả
Bạn lưu trữ nội bộ băm dưới dạng std::array<std::byte, size>. Đó là điều thích hợp để làm. Tôi không nghĩ rằng cần phải cung cấp bất kỳ hàm thành viên nào khác với một hàm giúp bạn consttham chiếu đến mảng đó. Người gọi có thể chuyển đổi nó sang bất kỳ hình thức nào theo cách của họ. A std::arrayđã hoàn toàn có thể chuyển đổi thành a std::span. Và một khi bạn có tham chiếu đến mảng, thật dễ dàng để lấy các trình vòng lặp bắt đầu và kết thúc từ nó.
Thêm toán tử so sánh
Khá phổ biến khi muốn kiểm tra xem hai hàm băm có giống nhau hay không, vì vậy sẽ hữu ích nếu ít nhất là xác định operator==()và operator!=()cho lớp lưu giữ kết quả thông báo.
Tôi nghĩ rằng tôi sẽ thay đổi giao diện.
Hiện tại kiểu sử dụng là:
typename Sha1::DigestStore digest;
Sha1 hasher;
hasher.hash("Bob", digest);
Dường như không cần tạo một Sha1đối tượng. Tôi nghĩ rằng một giao diện tốt hơn có thể là thực hiện tất cả các phương pháp staticđể việc sử dụng trở thành:
typename Sha1::DigestStore digest;
Sha1::hash("Bob", digest);
Có DigestStorethể cần một số chức năng truy cập khác. Nó hiện đang cho phép iterationnhưng có thể có một trường hợp sử dụng mà chúng tôi có const_iterator.
Vẫn đang cố gắng hiểu khi nào tốt nhất để sử dụng string_view. Thật không may, nó vẫn không chơi tốt với các dây bình thường. Vì vậy, chúng tôi có thể phải cung cấp một cách để trích xuất một chuỗi từ bộ đệm. Trong trường hợp đó, sẽ rất tốt nếu chúng ta có thể có dữ liệu từ DigestStore thành một chuỗi (có nghĩa là không sử dụng std::array) nhưng cần phải có một trường hợp sử dụng tốt để làm cho nó hoạt động tốt hơn.
Không chắc điều đó sẽ hoạt động như thế nào. Vui lòng cung cấp một gợi ý nếu bạn có ý tưởng.