关键词: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 用途:主要用于验证,防止信息被修.具体用途如:文件校验、数字签名、鉴权协议
总结安全
对称加密算法,速度快,安全性低,目前大量数据加密建议采用对称加密算法,提升加解密速度 非对称加密算法,速度较慢,安全性高,小量的机密数据,能够采用非对称加密算法。 实际工做中经常使用的方式是采用非对称加密算法管理对称算法的密钥,而后用对称加密算法加密数据,这样咱们就集成了两类加密算法的优势,既实现了加密速度快的优势,又实现了安全方便管理密钥的优势。
场景一 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模式 这个后续会继续介绍
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; }
签约和验签流程后续会补充