1. 程式人生 > >C++ token JWT HS256 生成token和驗證token

C++ token JWT HS256 生成token和驗證token

使用到的類庫

jsoncpp:用於json資料操作

boost:智慧指標和正則表達,如果是C++11以上版本可以替換成C++11的智慧指標和正則表示式

CryptoPP:大名鼎鼎的免費的C++加解密類庫,Crypto++幾乎涵蓋了所有的加解密演算法

以上類庫是可以跨平臺使用

程式碼

標頭檔案

#include <string>
#include  <strstream>
#include <boost/noncopyable.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/regex.hpp>
#include <json/json.h> #include "se_std.h" #include "se_conf.h" #include <base64.h> #include <hmac.h> #include <sha.h> using namespace std; #ifndef H_3695350A_4107_49D7_BE41_60F19549BD53 #define H_3695350A_4107_49D7_BE41_60F19549BD53 /* * Author: * Version:1.0 * Copyright: * Description: 生成token和驗證token * https://jwt.io/introduction/ * Create:2018-02-01 * LastChange:2018-02-01 */
class se_token : public boost::noncopyable { private: const int FLAG; CryptoPP::HMAC< CryptoPP::SHA256 > m_hs256; boost::scoped_ptr<Json::StreamWriter> m_jwrite; boost::scoped_ptr<Json::CharReader> m_jread; stringstream m_strbuf; string m_header, m_header_base64; Json::Value m_jheader; boost::regex m_reg_exp; char
m_numbuf[SE_MAX_NUMSTR]; public: se_token() :FLAG(CryptoPP::HashVerificationFilter::PUT_RESULT | CryptoPP::HashVerificationFilter::HASH_AT_END) { this->init(); }; se_token(const CryptoPP::SecByteBlock &key) :m_hs256(key, key.size()), FLAG(CryptoPP::HashVerificationFilter::PUT_RESULT | CryptoPP::HashVerificationFilter::HASH_AT_END) { this->init(); }; virtual ~se_token() {}; private: /*初始化*/ void init(); /*清除字元緩衝區內容*/ inline void clear(stringstream &strbuf) const { strbuf.str(""); strbuf.clear(); } private: void parse_token(const string &token, string &msg, string &b64payload, string &b64sig) const; public: /*設定HMAC key*/ inline void set_key(const CryptoPP::SecByteBlock &key) { this->m_hs256.SetKey(key, key.size()); } /*設定HMAC key(key為base64編碼)*/ void set_key_base64(const string &b64key); /*生成key*/ void generate_key(string &b64key) const; /*生成簽名*/ void signature(const string &plain, string &mac); /*生成簽名(簽名為base64編碼)*/ void signature_base64(const string &plain, string &b64); /*驗證簽名*/ bool verify(const string &msg, const string &signature); /*驗證簽名(簽名為base64編碼)*/ bool verify_base64(const string &msg, const string &b64); /*獲取JWT Header資訊*/ inline const string &header() const { return this->m_header; } /*獲取JWT Header資訊*/ inline const Json::Value &header_json() const { return this->m_jheader; } /* 根據json生成token * 注意,生成的token如要附件在url中必須採用url_encode後輸出 */ void generate_token(const Json::Value &payload, string &token); /* 根據payload json格式字串生成token * 注意,生成的token如要附件在url中必須採用url_encode後輸出 */ void generate_token(const string &payload, string &token); /*解析token*/ void parse_token(const string &token, Json::Value &payload); /*更新token的過期時間*/ void update_expires(const se_conf_server &conf, string &token); }; #endif /* H_3695350A_4107_49D7_BE41_60F19549BD53 */

原始檔

#include "se_token.h"
#include <osrng.h>

#define  HS256KEY ("eoRK6DnPRTbQ72oEG+ANFg==")

void se_token::init() {
    Json::StreamWriterBuilder writer;
    writer["commentStyle"] = "None";
    writer["indentation"] = "";  // or whatever you like    
    writer["precision"] = 6;
    this->m_jwrite.reset(writer.newStreamWriter());

    Json::CharReaderBuilder rbuilder;
    this->m_jread.reset(rbuilder.newCharReader());

    this->m_header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
    CryptoPP::StringSource(this->m_header, true,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(this->m_header_base64), false
        )
    );

    this->m_reg_exp = "(\\\"exp\\\")\\:(\\d+)";

    this->m_jheader["typ"] = "JWT";
    this->m_jheader["alg"] = "HS256";
    this->set_key_base64(HS256KEY);
}

void se_token::generate_key(string &b64key) const {
    CryptoPP::AutoSeededRandomPool prng;
    CryptoPP::SecByteBlock key(16);
    prng.GenerateBlock(key, key.size());

    CryptoPP::StringSource(key, key.size(), true,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(b64key), false
        )
    );
}

void se_token::set_key_base64(const string &b64key) {
    CryptoPP::SecByteBlock key;
    string encoded;
    CryptoPP::StringSource(b64key, true,
        new CryptoPP::Base64Decoder(
            new CryptoPP::StringSink(encoded)
        )
    );
    key.Assign((const byte *)encoded.c_str(), encoded.size());
    this->set_key(key);
}

void se_token::signature(const string &plain, string &mac) {
    mac.clear();
    CryptoPP::StringSource ss1(plain, true,
        new CryptoPP::HashFilter(this->m_hs256,
            new CryptoPP::StringSink(mac)
        )
    );

}

void se_token::signature_base64(const string &plain, string &b64) {
    string mac;
    b64.clear();
    this->signature(plain, mac);
    CryptoPP::StringSource(mac, true,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(b64), false
        )
    );
}

bool se_token::verify(const string &msg, const string &signature) {
    bool result = false;
    CryptoPP::StringSource ss(msg + signature, true,
        new CryptoPP::HashVerificationFilter(this->m_hs256,
            new CryptoPP::ArraySink((byte*)&result, sizeof(result)),
            FLAG
        )
    );
    return result;
}

bool se_token::verify_base64(const string &msg, const string &b64sig) {
    string signature;
    CryptoPP::StringSource(b64sig, true,
        new CryptoPP::Base64Decoder(
            new CryptoPP::StringSink(signature)
        )
    );
    return this->verify(msg, signature);
}

void se_token::generate_token(const Json::Value &payload, string &token) {
    this->clear(this->m_strbuf);
    ostream stream(this->m_strbuf.rdbuf());
    this->m_jwrite->write(payload, &stream);
    string str_payload = this->m_strbuf.str();
    this->clear(this->m_strbuf);

    string payload_base64, signature;
    CryptoPP::StringSource(str_payload, true,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(payload_base64), false
        )
    );

    token.clear();
    token.append(this->m_header_base64).append(".").append(payload_base64);
    this->signature_base64(token, signature);
    token.append(".").append(signature);
}

void se_token::generate_token(const string &payload, string &token) {
    string payload_base64, signature;
    CryptoPP::StringSource(payload, true,
        new CryptoPP::Base64Encoder(
            new CryptoPP::StringSink(payload_base64), false
        )
    );

    token.clear();
    token.append(this->m_header_base64).append(".").append(payload_base64);
    this->signature_base64(token, signature);
    token.append(".").append(signature);
}

void se_token::parse_token(const string &token, string &msg, string &b64payload, string &b64sig) const {
    string::size_type pos1 = token.find(".");
    string::size_type pos2 = token.find(".", pos1 + 1);
    if (string::npos == pos1 || string::npos == pos2)
        SE_THROW("invalid token!");
    msg = token.substr(0, pos2);
    ++pos1;
    b64payload = token.substr(pos1, pos2 - pos1);
    ++pos2;
    b64sig = token.substr(pos2);
}

void se_token::parse_token(const string &token, Json::Value &payload) {
    string msg, b64payload, b64sig;
    this->parse_token(token, msg, b64payload, b64sig);
    if (!this->verify_base64(msg, b64sig))
        SE_THROW("lllegal token!");

    string plian, tmp;
    CryptoPP::StringSource(b64payload, true,
        new CryptoPP::Base64Decoder(
            new CryptoPP::StringSink(plian)
        )
    );

    const char *ptr_start = plian.c_str();
    const char *ptr_end = ptr_start + plian.size();
    bool parsingSuccessful = this->m_jread->parse(ptr_start, ptr_end, &payload, &tmp);
    if (!parsingSuccessful)
        SE_THROW(tmp);
}

void se_token::update_expires(const se_conf_server &conf, string &token) {
    string msg, b64payload, b64sig;
    this->parse_token(token, msg, b64payload, b64sig);
    if (!this->verify_base64(msg, b64sig))
        SE_THROW("invalid token!");

    string plian;
    CryptoPP::StringSource(b64payload, true,
        new CryptoPP::Base64Decoder(
            new CryptoPP::StringSink(plian)
        )
    );

    /*獲取當前時間*/
    const time_t tt = time(NULL);
    time_t exp = tt + conf.expires();
#if defined(_MSC_VER)
    _snprintf_s(this->m_numbuf, SE_MAX_NUMSTR, "$1:%I64d", exp);
#else
    snprintf(this->m_numbuf, SE_MAX_NUMSTR, "$1:%lld", exp);
#endif
    //替換過期時間
    std::string result = boost::regex_replace(plian, this->m_reg_exp, this->m_numbuf);
    this->generate_token(result, token);
}

輸出

呼叫generate_token方法後的輸出結果(JWT)

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MTc0OTgwNzQsImlzcyI6ImttY2IuY29tIiwibmFtZSI6InVzZXJpZCIsInVzZXJpZCI6MX0=.u3lauh/Inw+fTq7kITL+5mwQQ65LP5t2g9CHXWQZkrk=

如果要直接在url中使用,還需要對上述字串進行url編碼,否則因url編碼的原因,會導致驗證失敗.

/*經過url編碼後的token,驗證成功*/
http://localhost:9101/heightservice?m=chatoken&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MTc0OTk5ODAsImlzcyI6ImttY2IuY29tIiwibmFtZSI6InVzZXJpZCIsInVzZXJpZCI6MX0%3D.EGK4P7wqMgm0HjwLuApkqVvyc5AZUJ6zvnQsBrqlhHM%3D