eos中籤名驗籤流程和eosjs中的加解密原理

關鍵詞:eos 簽名 驗籤 ecc dsa 加密 解密 eosjs aesjava

本文主要探討兩方面git

1.eosjs中用密鑰對進行加解密功能
2.eos中密鑰對生成,簽名和驗簽過程(私鑰簽名 公鑰驗籤)

經常使用的加密算法

對稱性加密算法算法

對稱式加密就是加密和解密使用同一個密鑰,信息接收雙方都需事先知道密匙和加解密算法,以後即是對數據進行加解密了.對稱加密算法用來對敏感數據等信息進行加密。

對稱性加密算法有:AES、DES、3DES
DES(Data EncryptionStandard):數據加密標準,速度較快,適用於加密大量數據的場合.

3DES(Triple DES):是基於DES,對一塊數據用三個不一樣的密鑰進行三次加密,強度更高.

AES(Advanced EncryptionStandard):高級加密標準,是下一代的加密算法標準,速度快,安全級別高;AES是一個使用128爲分組塊的分組加密算法,分組塊和128 192或256位的密鑰一塊兒做爲輸入,對4×4的字節數組上進行操做.衆所周之AES是種十分高效的算法,尤爲在8位架構中,這源於它面向字節的設計.AES 適用於8位的小型單片機或者普通的32位微處理器,而且適合用專門的硬件實現,硬件實現可以使其吞吐量(每秒能夠到達的加密/解密bit數)達到十億量級.一樣,其也適用於RFID系統

非對稱算法api

非對稱式加密就是加密和解密所使用的不是同一個密鑰,一般有兩個密鑰,稱爲"公鑰"和"私鑰",它們兩個必需配對使用,不然不能打開加密文件.發送雙方A,B事先均生成一堆密匙,而後A將本身的公有密匙發送給B,B將本身的公有密匙發送給A,若是A要給B發送消息,則先須要用B的公有密匙進行消息加密,而後發送給B端,此時B端再用本身的私有密匙進行消息解密,B向A發送消息時爲一樣的道理

非對稱性算法有:RSA、DSA、ECC

RSA:由 RSA 公司發明,是一個支持變長密鑰的公共密鑰算法,須要加密的文件塊的長度也是可變的.RSA在國外早已進入實用階段,已研製出多種高速的RSA的專用芯片.

DSA(Digital SignatureAlgorithm):數字簽名算法,是一種標準的DSS(數字簽名標準),嚴格來講不算加密算法.

ECC(Elliptic CurvesCryptography):橢圓曲線密碼編碼學.ECC和RSA相比具備多方面的絕對優點,主要有:抗攻擊性強.相同的密鑰長度,其抗攻擊性要強不少倍.計算量小,處理速度快.ECC總的速度比RSA、DSA要快得多.存儲空間佔用小.ECC的密鑰尺寸和系統參數與RSA、DSA相比要小得多,意味着它所佔的存貯空間要小得多.這對於加密算法在IC卡上的應用具備特別重要的意義.帶寬要求低.當對長消息進行加解密時,三類密碼系統有相同的帶寬要求,但應用於短消息時ECC帶寬要求卻低得多.帶寬要求低使ECC在無線網絡領域具備普遍的應用前景

散列算法數組

散列算法,是一種單向的不可逆的加密算法.它對不一樣長度的輸入消息,產生固定長度的輸出.多用於網絡傳輸過程驗證數據的完整性

散列算法(簽名算法)有:MD五、SHA1
用途:主要用於驗證,防止信息被修.具體用途如:文件校驗、數字簽名、鑑權協議

總結安全

對稱加密算法,速度快,安全性低,目前大量數據加密建議採用對稱加密算法,提升加解密速度
非對稱加密算法,速度較慢,安全性高,小量的機密數據,能夠採用非對稱加密算法。

實際工做中經常使用的方式是採用非對稱加密算法管理對稱算法的密鑰,而後用對稱加密算法加密數據,這樣咱們就集成了兩類加密算法的優勢,既實現了加密速度快的優勢,又實現了安全方便管理密鑰的優勢。

eosjs中用密鑰對進行加解密功能

場景一
AES的Key通過接收方公鑰加密和AES加密的內容 一塊兒發送給接收方,接收方經過本身私鑰先將加密後的AES_KEY解密,再經過解密獲得的原始AES_KEY,並用該key解密發送方發送的內容,獲得明文

結論 這種算法是目前市面上經常使用的,既增長了安全性又提高了加密速度

場景二
先來看一下ECC 數學函數 Q=dG; (Q是公鑰 d是私鑰 G是他們之間的關係);Q1 = d1G1; Q2=d2G2;那麼能推出 key=Q1d2G2 = Q2d1G1;

結論 1的公鑰和2的私鑰 2的公鑰和1的私鑰 他們能獲得一個相同的值 key,這個相同的key 做爲他們之間AES加解密的key eosjs就是經過這種方式實現的    

經過對eosjs中ecc庫的測試 結果跟上邊的場景二是一致的

咱們經過分析源碼來看一下網絡

someonesPrivateKey = ecc.seedPrivate("someone");
someonesPublicKey = ecc.privateToPublic(someonesPrivateKey);

console.log('someonesPrivateKey:\t', someonesPrivateKey.toString())
console.log('someonesPublicKey:\t', someonesPublicKey.toString())

myPrivate = ecc.seedPrivate("my");
myPublic = ecc.privateToPublic(myPrivate);

console.log('myPrivate:\t', myPrivate.toString())
console.log('myPublic:\t', myPublic.toString())

encryptedMessage = ecc.Aes.encrypt(myPrivate, someonesPublicKey, message)//MY用本身的私鑰和someone的公鑰進行加密
decryptedMessage = ecc.Aes.decrypt(someonesPrivateKey, myPublic, encryptedMessage.nonce, encryptedMessage.message, encryptedMessage.checksum)//someone用本身的私鑰和my的公鑰進行解密

隨機生成兩對密鑰對
最重要的是加密encrypt和解密decrypt這兩個函數架構

function encrypt(private_key, public_key, message) {
   var nonce = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : uniqueNonce();

   return crypt(private_key, public_key, nonce, message);
}

function decrypt(private_key, public_key, nonce, message, checksum) {
   return crypt(private_key, public_key, nonce, message, checksum).message;
}

他們兩個其實都是經過一個crypt函數實現的,咱們看一下crypt的具體實現流程app

function crypt(private_key, public_key, nonce, message, checksum) {
   
   private_key = PrivateKey(private_key);//檢查私鑰的合理性,是否符合sha256x2, K1等算法要求,這一部分稍後會詳細的跟蹤一下
   if (!private_key) throw new TypeError('private_key is required');

   public_key = PublicKey(public_key);//驗證公鑰的合法性,這裏對公鑰進行了去除EOS頭的處理
   if (!public_key) throw new TypeError('public_key is required');


   nonce = toLongObj(nonce);//隨機或惟一uint64在從新使用相同的私鑰/公鑰時提供熵,這個nonce是經過時間生成的,我測試了若是 不加這個邏輯加解密也沒問題,應該是增長安全性的吧,後續會再繼續跟蹤
   if (!nonce) throw new TypeError('nonce is required');
   console.log('nonce:\t', nonce.toString())

   //這一部分是將要加密的格式轉換成封裝的Buffer格式,Buffer支持"ascii" | "utf8" | "utf16le" | "ucs2" | "base64" | "latin1" | "binary" | "hex"編碼格式
   if (!Buffer.isBuffer(message)) {
       if (typeof message !== 'string') throw new TypeError('message should be buffer or string');
       message = new Buffer(message, 'binary');
   }
   if (checksum && typeof checksum !== 'number') throw new TypeError('checksum should be a number');

   var S = private_key.getSharedSecret(public_key);//獲取共享密鑰key,這個key在函數中調用了一次hash.sha512
   var ebuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);//
   ebuf.writeUint64(nonce);
   ebuf.append(S.toString('binary'), 'binary');
   ebuf = new Buffer(ebuf.copy(0, ebuf.offset).toBinary(), 'binary');
   var encryption_key = hash.sha512(ebuf);//對數據又進行了一次hash

   // D E B U G
   console.log('crypt', {
       priv_to_pub: private_key.toPublic().toString(),
       pub: public_key.toString(),
       nonce: nonce.toString(),
       message: message.length,
       checksum,
       S: S.toString('hex'),
       encryption_key: encryption_key.toString('hex'),
   })

   var iv = encryption_key.slice(32, 48);//獲取AES須要的IV初始向量
   var key = encryption_key.slice(0, 32);//獲取AES的密鑰
   
   //獲取共享祕密校驗和,這個用來校驗兩端獲取的key是否一致
   var check = hash.sha256(encryption_key);
   check = check.slice(0, 4);
   var cbuf = ByteBuffer.fromBinary(check.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN);
   check = cbuf.readUint32();


   if (checksum) {
       if (check !== checksum) throw new Error('Invalid key');
       message = cryptoJsDecrypt(message, key, iv);//aes解密
   } else {
       message = cryptoJsEncrypt(message, key, iv);//aes加密
   }
   return { nonce: nonce, message: message, checksum: check };
}

這裏邊最重要的一個函數 就是getSharedSecret,經過這個接口 就實現了場景二中提到的 1的公鑰和2的私鑰 2的公鑰和1的私鑰 他們能獲得一個相同的key
看一下他內部作了一些什麼操做函數

function getSharedSecret(public_key) {
       public_key = PublicKey(public_key);//驗證公鑰的合法性,這裏對公鑰進行了去除EOS頭的處理
       var KB = public_key.toUncompressed().toBuffer();//獲取公開密鑰K
       var KBP = Point.fromAffine(secp256k1, BigInteger.fromBuffer(KB.slice(1, 33)), // x
       BigInteger.fromBuffer(KB.slice(33, 65)) // y
       );//獲取K的橢圓曲線上的映射點(x,y)
       var r = toBuffer();
       var P = KBP.multiply(BigInteger.fromBuffer(r));//KBP對應的(x,y)分別和r進行相乘獲取新的點point
       var S = P.affineX.toBuffer({ size: 32 });//新生成的point的X座標
       // SHA512 used in ECIES
       return hash.sha512(S);
   }

具體的aes加密和解密函數

function cryptoJsDecrypt(message, key, iv) {
   message.toString(),key.toString('hex'), iv.toString('hex'))
   assert(message, "Missing cipher text");
   message = toBinaryBuffer(message);
   var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
   // decipher.setAutoPadding(true)
   message = Buffer.concat([decipher.update(message), decipher.final()]);
   return message;
}

function cryptoJsEncrypt(message, key, iv) {
   message.toString(),key.toString('hex'), iv.toString('hex'))
   assert(message, "Missing plain text");
   message = toBinaryBuffer(message);
   var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
   // cipher.setAutoPadding(true)
   message = Buffer.concat([cipher.update(message), cipher.final()]);
   return message;
}

對應java也有封裝的庫能夠調用
對應的aes-256-cbc爲aes的256位的cbc模式 這個後續會繼續介紹

eos中密鑰對生成,簽名和驗簽過程

1.密鑰對生成

當咱們在終端輸入下面命令時 能夠獲取一對密鑰對

wls@wls-TM1701:~/9f-git/eosjs/api/local$ cleos create key --to-console
Private key: 5HptWorg6Q8ao3i7i1AjWnEoou1AZwoaTEkfpo1LeXrT9afBawS
Public key: FZS7BoDwkm4oZiWobX3HH9wtJ27a42RQuJm6en2ZnJfX1K4yDyKTV

讓咱們看一下內部是如何實現的
cleos對應main.cpp中以下代碼

bool r1 = false;
  string key_file;
  bool print_console = false;
  // create key
  auto create_key = create->add_subcommand("key", localized("Create a new keypair and print the public and private keys"))->set_callback( [&r1, &key_file, &print_console](){
     if (key_file.empty() && !print_console) {
        std::cerr << "ERROR: Either indicate a file using \"--file\" or pass \"--to-console\"" << std::endl;//當--file和--to-console都沒有指定的時候 退出
        return;
     }

    auto pk    = r1 ? private_key_type::generate_r1() : private_key_type::generate();//默認K1方式 當參數制定--ri時 採用r1方式
     auto privs = string(pk);
     auto pubs  = string(pk.get_public_key());
     if (print_console) {
        std::cout << localized("Private key: ${key}", ("key",  privs) ) << std::endl;
        std::cout << localized("Public key: ${key}", ("key", pubs ) ) << std::endl;
     } else {
        std::cerr << localized("saving keys to ${filename}", ("filename", key_file)) << std::endl;
        std::ofstream out( key_file.c_str() );
        out << localized("Private key: ${key}", ("key",  privs) ) << std::endl;
        out << localized("Public key: ${key}", ("key", pubs ) ) << std::endl;
     }
  });

ecc的ri和k1方式區別在哪兒 咱們繼續跟蹤

template< typename KeyType = ecc::private_key_shim >//這裏是定義在ecc命令空間中
        static private_key generate() {
           return private_key(storage_type(KeyType::generate()));
        }

        template< typename KeyType = r1::private_key_shim >//這裏定義在fc::ri的命名空間中
        static private_key generate_r1() {
           return private_key(storage_type(KeyType::generate()));
        }

能夠兩個定義在不一樣的命名空間中
下面咱們具體分析這兩種命名空間中具體代碼的實現區別

ecc命名空間中的調用代碼

private_key private_key::generate()
    {
       EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 );//首先經過橢圓曲線的標識符NID_secp256k1生成一個EC_KEY,經過這種方式生成的EC_KEY裏已經包含了橢圓曲線的參數。不然,須要手動設置EC_GROUP
       if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" );
       if( !EC_KEY_generate_key( k ) )//生成私鑰和公鑰
       {
          FC_THROW_EXCEPTION( exception, "ecc key generation error" );

       }

       return private_key( k );
    }

ri命名空間中的實現代碼

private_key private_key::generate()
   {
      private_key self;
      EC_KEY* k = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 );//首先經過橢圓曲線的標識符NID_X9_62_prime256v1生成一個EC_KEY,經過這種方式生成的EC_KEY裏已經包含了橢圓曲線的參數。不然,須要手動設置EC_GROUP
      if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" );
      self.my->_key = k;
      if( !EC_KEY_generate_key( self.my->_key ) )//生成私鑰和公鑰
      {
         FC_THROW_EXCEPTION( exception, "ecc key generation error" );

      }

經過跟蹤標識符的定義發現還有好多種不一樣的曲線算法,不一樣的橢圓曲線只有參數上的不一樣。因此,算出正確簽名的前提是設置正確的參數

fc::sha256 private_key::get_secret( const EC_KEY * const k )
   {
      if( !k )
      {
         return fc::sha256();
      }

      fc::sha256 sec;
      const BIGNUM* bn = EC_KEY_get0_private_key(k);
      if( bn == NULL )
      {
        FC_THROW_EXCEPTION( exception, "get private key failed" );
      }
      int nbytes = BN_num_bytes(bn);
      BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] );
      return sec;
   }

簽約和驗籤流程後續會補充

相關文章
相關標籤/搜索