PHP与OpenSSL工具包 AES+RSA

OpenSSL readme ========================================

OpenSSL加密解密工具包

linux 需要安装openssl工具包,传送门 http://www.openssl.org/source/
window 下需要安装openssl的程序,传送门 http://slproweb.com/products/Win32OpenSSL.html
ASN.1 key structures in DER and PEM - https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
RSA算法原理(一)阮一峰 - http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
RSA算法原理(二)阮一峰 - http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html

开始之前需要将php.ini配置文件的;extension=php_openssl.dll 改为 extension=php_openssl.dll。

RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中RSA被广泛使用。非对称指的是加密解密用的是不同的一组密钥,这就是与对称加密的最大区别。非对称加密算法的实现使得密码可以明文传输而没有泄密风险,基本原理是:

+ A与B双方生成各自的公钥私钥
+ 双方交换公钥,可以明文传输
+ 各方用对方提供的公钥加密消息后发送给对方,这个密文只有拥有密钥方才能解开
+ 只要密钥不泄漏可保公钥明文传输的安全性

RSA是1977年由麻省理工学院的罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起设计的,RSA就是他们三人姓氏开头字母拼在一起组成的。

在非对称加密系统出现之前,所有加密和解密使用同样规则,这些规则相当于密钥,称为对称加密算法(Symmetric-key algorithm)。其中又以高级加密标准为代表(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

密钥生成

openssl genrsa 用于生成rsa私钥文件,生成是可以指定私钥长度,具体参数请参考文档。

openssl genrsa -out 2048_rsa_private_key.pem 2048 

Rsa命令用于处理Rsa密钥生成公钥、格式转换和打印信息

openssl rsa -in 2048_rsa_private_key.pem -pubout -out 2048_rsa_public_key.pem 

-in filename:输入的RSA密钥文件,在此为上面生成的密钥 rsa_private_key.pem。
-pubout:设置此选项后,保存公钥值到输出文件中。
-out filename:输出文件,在此我们定义成rsa_public_key.pem

java 开发使用的 PKCS8 格式转换命令

openssl pkcs8 -topk8 -inform PEM -in 2048_rsa_private_key.pem -outform PEM -nocrypt -out 2048_rsa_private_key_pkcs8.pem

PHP与OpenSSL AES对称加密

openssl_encrypt()
openssl_decrypt() 

微信公众平台/小程序使用的AES算法是 AES-128-CBC + OPENSSL_RAW_DATA。

信息摘要算法

Message Digest Algorithm 消息摘要算法缩写为MD,一种被广泛使用的密码散列函数,其中以 MD5消息摘要算法为普遍。
Secure Hash Algorithm 缩写为SHA,密码散列函数。能计算出一个数字消息所对应到的,固定长度字符串的算法,也是消息摘要算法的一种。

这些算法(md,sha)之所以称作安全算法基于以下两点:
(1)由消息摘要反推原输入消息,从计算理论上来说是很困难的。但目前有人制造出碰撞的可能了,大大减弱了安全性。
(2)想要找到两组不同的消息对应到相同的消息摘要,从计算理论上来说是很困难的。任何对输入消息的变动,都会很高概率导致其产生的消息摘要迥异。

HMAC:散列消息身份验证码 Hashed Message Authentication Code 。

根据RFC 2316,HMAC以及IPSec被认为是Interact安全的关键性核心协议。它不是散列函数,而是采用了将MD5或SHA1散列函数与共享机密密钥(与公钥/私钥对不同)一起使用的消息身份验证机制。基本来说,消息与密钥组合并运行散列函数。然后运行结果与密钥组合并再次运行散列函数。这个128位的结果被截断成96位,成为MAC。然后创建两个B长的不同字符串:

innerpad = 长度为B的 0×36
outterpad = 长度为B的 0×5C
计算输入字符串str的HMAC:
hash(key ^ outterpad, hash(key ^ innerpad, str))

hmac主要应用在身份验证中,它的使用方法是这样的:

1. 客户端发出登录请求(假设是浏览器的GET请求)
2. 服务器返回一个随机值,并在会话中记录这个随机值
3. 客户端将该随机值作为密钥,用户密码进行hmac运算,然后提交给服务器
4. 服务器读取用户数据库中的用户密码和步骤2中发送的随机值做与客户端一样的hmac运算,然后与用户发送的结果比较,如果结果一致则验证用户合法

在这个过程中,可能遭到安全攻击的是服务器发送的随机值和用户发送的hmac结果,而对于截获 了这两个值的黑客而言这两个值是没有意义的,绝无获取用户密码的可能性,随机值的引入使hmac只在当前会话中有效,大大增强了安全性和实用性。大多数的 语言都实现了hmac算法,比如php的mhash、python的hmac.py、java的MessageDigest类,在web验证中使用 hmac也是可行的,用js进行md5运算的速度也是比较快的。

PHP与OpenSSL RSA非对称加解密

RSA使用非对称加解密字符长度是 密钥长度/8bit=字节的长度,如1024对应的数据分组长度128字节,2048对数据分组256字节。 RSA加密解密有四个配置的方法,使用私钥加密就对应公钥解密,反之公钥加密就用私钥解密,配套使用。

openssl_private_encrypt() - Encrypts data with private key
openssl_private_decrypt() - Decrypts data with private key
openssl_public_encrypt() - Encrypts data with public key
openssl_public_decrypt() - Decrypts data with public key

PEM密钥文件读取配套方法

openssl_pkey_get_private(file_get_contents($path)); 
openssl_pkey_get_public(file_get_contents($path)); 

OpenSSL模块提供丰富的功能,包括密钥生成API都有。

签名与验证

使用配套方法

openssl_sign()
openssl_verify()

注意,阿里支付使用的签名算法是 OPENSSL_ALGO_SHA256,默认的是 OPENSSL_ALGO_SHA1。

完整参考代码:

class Crypto{

    const KEYSIZE = 2048;
    const CONF = 'alipay/openssl/openssl.cnf';
    const PRIVATEKEY = "./ranking/rsa/2048_private_key.pem";
    const PUBLICKEY  = "./ranking/rsa/2048_public_key.pem";

    static function keygen(){
        // window系统要设置openssl环境变量或通过配置信息指定配置文件
        $conf = array(
            'private_key_bits' => self::KEYSIZE,
            'config' => self::CONF,
        );
        $res = openssl_pkey_new($conf);
        if( $res ) {
            $d= openssl_pkey_get_details($res);
            $pub = $d['key'];
            $bits = $d['bits'];
            $filepath = $bits.'_rsa_private_key.pem';
            openssl_pkey_export($res, $pri, null, $conf);
            openssl_pkey_export_to_file($res, $filepath, null, $conf);
            print_r(["private_key"=>$pri, "public_key"=>$pub, "keysize"=>$bits]);
        }else echo "openssl_pkey_new falls";
    }

    static function encrypt($msg, $key, $method="AES-128-CBC", $options=OPENSSL_RAW_DATA){
        $ivlen  = openssl_cipher_iv_length($method);
        $iv     = openssl_random_pseudo_bytes($ivlen);
        $cipher = openssl_encrypt($msg, $method, $key, $options, $iv);
        $hmac   = hash_hmac('sha256', $cipher, $key, $as_binary=true);
        $cipher = base64_encode( $iv.$hmac.$cipher );
        return $cipher;
    }

    static function decrypt($cipher, $key, $method="AES-128-CBC", $options=OPENSSL_RAW_DATA){
        $c       = base64_decode($cipher);
        $ivlen   = openssl_cipher_iv_length($method);
        $iv      = substr($c, 0, $ivlen);
        $hmac    = substr($c, $ivlen, $sha2len=32);
        $cipher  = substr($c, $ivlen+$sha2len);
        $msg     = openssl_decrypt($cipher, $method, $key, $options, $iv);
        $calcmac = hash_hmac('sha256', $cipher, $key, $as_binary=true);
        if( hash_equals($hmac, $calcmac) ) return $msg;//PHP 5.6+ timing attack safe comparison
        return false;
    }

    static function getPublicKey()
    {
        $pem = file_get_contents(self::PUBLICKEY);
        // $pem = chunk_split(base64_encode($pem),64,"\n"); // transfer to pem format
        // $pem = "-----BEGIN CERTIFICATE-----\n".$pem."-----END CERTIFICATE-----\n";
        $publicKey = openssl_pkey_get_public($pem);
        return $publicKey;
    }
    
    static function getPrivateKey()
    {
        $pem = file_get_contents(self::PRIVATEKEY);
        // $pem = chunk_split($pem,64,"\n"); // transfer to pem format
        // $pem = "-----BEGIN PRIVATE KEY-----\n".$pem."-----END PRIVATE KEY-----\n";
        $privateKey = openssl_pkey_get_private($pem);
        return $privateKey;
    }
    
    static function sign($msg, $algorithm=OPENSSL_ALGO_SHA256){
        $sign = "";
        $key = self::getPrivateKey();
        // OPENSSL_ALGO_SHA256 OPENSSL_ALGO_MD5 OPENSSL_ALGO_SHA1
        openssl_sign($msg, $sign, $key, $algorithm);
        $sign = base64_encode($sign);
        openssl_free_key($key);
        return $sign;
    }
    
    static function verify($msg, $sign, $algorithm=OPENSSL_ALGO_SHA256){
        $sign = base64_decode($sign);
        $key = self::getPublicKey();
        $result = openssl_verify($msg, $sign, $key, $algorithm);
        openssl_free_key($key);
        return $result;
    }

    static function publicEncrypt($source_data) {
        $data = "";
        $key = self::getPublicKey();
        $dataArray = str_split($source_data, self::KEYSIZE/8);
        foreach ($dataArray as $value) {
            $encryptedTemp = ""; 
            openssl_public_encrypt($value,$encryptedTemp,$key);
            $data .= $encryptedTemp;
        }
        openssl_free_key($key);
        return base64_encode($data);
    }
    
    static function privateDecrypt($eccryptData) {
        $decrypted = "";
        $decodeStr = base64_decode($eccryptData);
        $key = self::getPrivateKey();
        $enArray = str_split($decodeStr, self::KEYSIZE/8);

        foreach ($enArray as $va) {
            $decryptedTemp = "";
            openssl_private_decrypt($va,$decryptedTemp,$key);
            $decrypted .= $decryptedTemp;
        }
        openssl_free_key($key);
        return $decrypted;
    }

    static function privateEncrypt($source_data) {
        $data = "";
        $dataArray = str_split($source_data, self::KEYSIZE/8);
        $key = self::getPrivateKey();
        foreach ($dataArray as $value) {
            $encryptedTemp = ""; 
            openssl_private_encrypt($value,$encryptedTemp,$key);
            var_dump( strlen($encryptedTemp));
            $data .= $encryptedTemp;
        }
        openssl_free_key($key);
        return base64_encode($data);
    }

    static function publicDecrypt($eccryptData) {
        $decrypted = "";
        $decodeStr = base64_decode($eccryptData);
        $key = self::getPublicKey();
        $enArray = str_split($decodeStr, self::KEYSIZE/8);

        foreach ($enArray as $va) {
            $decryptedTemp = "";
            openssl_public_decrypt($va,$decryptedTemp,$key);
            $decrypted .= $decryptedTemp;
        }
        openssl_free_key($key);
        return $decrypted;
    }
    
}

$plain  = "Some secret here for you ...";
$key    = openssl_random_pseudo_bytes(16);
$cipher = Crypto::encrypt($plain, $key);
$msg    = Crypto::decrypt($cipher, $key);
print_r(['明文'=>$plain, '解密'=>$msg, '密文'=>$cipher]);

$plain  = "利用公钥加密,私钥解密做数据保密通信!";
$cipher = Crypto::publicEncrypt($plain);
$msg    = Crypto::privateDecrypt($cipher);
print_r(['明文'=>$plain, '解密'=>$msg, '密文'=>$cipher]);

$plain  = "利用私钥加密,公钥解密可以做身份验证";
$cipher = Crypto::privateEncrypt($plain);
$msg    = Crypto::publicDecrypt($cipher);
print_r(['明文'=>$plain, '解密'=>$msg, '密文'=>$cipher]);

$msg    = 'a=123';
$sign   = Crypto::sign($msg);
$verify = Crypto::verify($msg, $sign);
print_r(['预签'=>$msg, '签名'=>$sign, '验证'=>$verify==1?"PASS":"FAIL"]);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342