C ++ Crypto: ตอนที่ 1- แฮช

Aug 26 2020

กำลังมองหาห้องสมุด Crypto ที่ทันสมัย
ไม่พบสิ่งที่ดี

ฉันรู้ว่าฉันอาจจะทำผิดทั้งหมดจึงทำงานกับฉันที่นี่ จะมีบทวิจารณ์ที่แตกต่างกันสี่แบบสำหรับโครงสร้างสี่แบบที่สร้างต่อกัน:

  1. แฮช
  2. แฮชคีย์
  3. รหัสผ่าน
  4. การตอบสนองความท้าทายเค็ม

นี่คือรหัสการแฮชและมีกระดาษห่อหุ้มรอบ ๆ SHA-1 และ SHA-256 แต่รูปแบบนั้นง่ายพอที่เราจะขยายสำหรับกลไกการแฮชอื่น ๆ ได้

โครงสร้างข้อมูลและการดำเนินงานที่นำเสนอในคำถามเหล่านี้จะขึ้นอยู่กับRFC2104และโพสต์ในcodeproject

ตัวอย่างการใช้งาน:

DigestStore<Sha1>    hash;    // <- destination of hash
Sha1                 hasher;

hasher.hash("This string can be hashsed", hash);

แฮช

#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

คำตอบ

3 G.Sliepen Aug 27 2020 at 03:58

ตัวพิมพ์ใหญ่ที่เหมาะสมของ SHA1

ชื่ออัลกอริทึมคือ SHA1 ไม่ใช่ Sha1 ดังนั้นฉันคิดว่าควรใช้ตัวพิมพ์ใหญ่ทั้งหมดที่นี่ นั่นทำให้การจับโค้ดสำหรับอัลกอริทึมเฉพาะได้ง่ายขึ้น

คุณต้องการเพียงหนึ่งคลาสต่ออัลกอริทึมแฮช

ตามที่คุณได้กล่าวไว้ในคำตอบของคุณเองSha1ชั้นเรียนดูเหมือนจะไม่จำเป็นเนื่องจากไม่ได้เก็บสถานะใด ๆ อย่างไรก็ตามแทนที่จะสร้างฟังก์ชันคงที่ภายในSha1เนมสเปซคุณสามารถทำให้ฟังก์ชันเหล่านั้นเป็นฟังก์ชันสมาชิกของคลาสที่มีสถานะจริงได้ สิ่งนี้หลีกเลี่ยงการทำซ้ำประเภท ตัวอย่างเช่น:

Sha1::DigestStore digest;
Sha1::hash("Bob", digest)

กลายเป็น:

Sha1::DigestStore digest;
digest.hash("Bob");

Sha1::Digest เทียบกับ Digest<Sha1>

ฉันคิดว่าการมีเนมสเปซที่Sha1มีDigestStoreฟังก์ชันa และภายในเป็นตัวเลือกที่ไม่ดี คุณสามารถทำกับ SHA1 ได้มากกว่าการสร้างแฮชธรรมดาเช่นคุณอาจต้องการสร้างHMACแทนแฮชธรรมดา ดังนั้นคุณจะต้องเพิ่มฟังก์ชันเพื่อสร้าง HMAC ให้กับแต่ละเนมสเปซที่ใช้อัลกอริทึมแฮช จะดีกว่ามากที่จะมีคลาสDigestและHMACเทมเพลตบนอัลกอริทึมแฮช

อนุญาตให้อัปเดตแฮช

โค้ดที่คุณเขียนจะทำการแปลงเพียงครั้งเดียวของอินพุตบางส่วนไปยังแฮช อย่างไรก็ตามไม่ใช่เรื่องแปลกที่โปรแกรมจะไม่มีข้อมูลทั้งหมดที่ต้องการสร้างแฮชในพื้นที่หน่วยความจำเดียวที่ต่อเนื่องกัน ในกรณีดังกล่าวคุณต้องการเขียน:

std::ostream output;
Digest<SHA1> digest;

digest.add("Header");
digest.add("Data");
digest.add("Footer");

output << digest.view();

อัลกอริธึมการย่อยบางอย่างอาจต้องการให้คุณเรียกใช้ฟังก์ชันบางอย่างเพื่อคำนวณค่าแฮชสุดท้ายหลังจากเพิ่มข้อมูลทั้งหมด คุณสามารถเพิ่มfinish()ฟังก์ชันที่ชัดเจนหรือเรียกสิ่งนี้โดยปริยายเมื่อเข้าถึงผลสรุป

รับผลลัพธ์ออกมา

คุณเก็บแฮชไว้ภายในเป็นไฟล์std::array<std::byte, size>. นั่นเป็นสิ่งที่ควรทำ ฉันไม่คิดว่าจำเป็นต้องให้ฟังก์ชันสมาชิกใด ๆ นอกเหนือจากฟังก์ชันที่ทำให้คุณได้รับการconstอ้างอิงถึงอาร์เรย์นั้น ขึ้นอยู่กับผู้โทรที่จะแปลงเป็นรูปแบบใดก็ได้ A std::arrayสามารถแปลงเป็นstd::spanไฟล์. และเมื่อคุณมีการอ้างอิงถึงอาร์เรย์แล้วการเริ่มต้นและสิ้นสุดตัววนซ้ำจากอาร์เรย์นั้นเป็นเรื่องง่าย

เพิ่มตัวดำเนินการเปรียบเทียบ

เป็นเรื่องปกติที่จะต้องการตรวจสอบว่าแฮชสองอันเหมือนกันหรือไม่ดังนั้นอย่างน้อยก็จะเป็นประโยชน์ในการกำหนดoperator==()และoperator!=()คลาสที่เก็บผลสรุป

1 MartinYork Aug 26 2020 at 05:27

ฉันคิดว่าฉันจะเปลี่ยนอินเทอร์เฟซ

ปัจจุบันรูปแบบการใช้งานคือ:

typename Sha1::DigestStore      digest;
Sha1                            hasher;

hasher.hash("Bob", digest);

ดูเหมือนจะไม่จำเป็นต้องสร้างSha1วัตถุ ฉันคิดว่าอินเทอร์เฟซที่ดีกว่าอาจจะทำให้ทุกวิธีstaticการใช้งานกลายเป็น:

typename Sha1::DigestStore      digest;

Sha1::hash("Bob", digest);

DigestStoreอาจต้องมีฟังก์ชั่นการเข้าถึงอื่น ๆ ปัจจุบันอนุญาตiterationแต่อาจมีกรณีการใช้งานที่เรามีไฟล์const_iterator.

string_viewยังคงพยายามที่จะเข้าใจเมื่อดีที่สุดในการใช้งาน น่าเสียดายที่มันยังเล่นได้ไม่ดีกับสายปกติ ดังนั้นเราอาจต้องเตรียมวิธีในการดึงสตริงออกจากบัฟเฟอร์ ในกรณีนี้คงจะดีไม่น้อยหากเราสามารถมีข้อมูลจาก DigestStore เป็นสตริง (ซึ่งหมายถึงไม่ได้ใช้งานstd::array) แต่จำเป็นต้องมี use case ที่ดีเพื่อให้ทำงานได้ดีขึ้น

ยังไม่แน่ใจว่าจะได้ผล โปรดระบุคำใบ้หากคุณมีความคิด