PHP+JAVA實現(xiàn)RSA互通加密解密

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

先學(xué)習(xí)一下漢語,密鑰[yuè]被辑,這個讀音表示開鎖或上鎖的用具盼理,來一起蜜月......

本篇將前面寫的《PHP與OpenSSL工具包 AES+RSA》一并整合,與Java版本的RSA來個聯(lián)合工作奏路,實現(xiàn)RSA加密解密的互通鸽粉。

不想下載工具的可以直接下載示例代碼:
DEMO鏈接:https://pan.baidu.com/s/153fi8ks3uk9_l5b9_gcRbQ
提取碼:fepm

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加密算法是一種非對稱加密算法位他,在公開密鑰加密和電子商業(yè)中RSA被廣泛使用。RSA是1977年由麻省理工學(xué)院的羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起設(shè)計的迈勋,RSA就是他們?nèi)诵帐祥_頭字母拼在一起組成的醋粟。非對稱指的是加密解密用的是不同的一組密鑰米愿,這就是與對稱加密的最大區(qū)別育苟。非對稱加密算法的實現(xiàn)使得密碼可以明文傳輸而沒有泄密風險,基本原理是:

+ A與B雙方生成各自的公鑰私鑰
+ 雙方交換公鑰博烂,可以明文傳輸
+ 各方用對方提供的公鑰加密消息后發(fā)送給對方禽篱,這個密文只有擁有密鑰方才能解開
+ 只要密鑰不泄漏可保公鑰明文傳輸?shù)陌踩?

RSA 加密或簽名后的結(jié)果是不可讀的二進制,使用時經(jīng)常會轉(zhuǎn)為 BASE64 碼再傳輸玛界。

RSA 加密時慎框,對要加密數(shù)據(jù)的大小有限制鲤脏,最大不大于密鑰長度吕朵。例如在使用 1024 bit 的密鑰時(genrsa -out rsa_private_key.pem 1024)努溃,最大可以加密 1024/8=128 Bytes 的數(shù)據(jù)。數(shù)據(jù)大于 128 Bytes 時沦疾,需要對數(shù)據(jù)進行分組加密(如果數(shù)據(jù)超限哮塞,加解密時會失敗忆畅,openssl 函數(shù)會返回 false)尸执,分組加密后的加密串拼接成一個字符串后發(fā)送給客戶端如失。

為了保證每次加密的結(jié)果都不同褪贵,RSA 加密時會在待加密數(shù)據(jù)后拼接一個隨機字符串,再進行加密板惑。不同的填充方式 Padding 表示這個字符串的不同長度冯乘,在對超限數(shù)據(jù)進行分組后,會按照這個 Padding 指定的長度填入隨機字符串姊氓。例如如果 Padding 填充方式使用默認的 OPENSSL_PKCS1_PADDING(需要占用 11 個字節(jié)用于填充)翔横,那么明文長度最多只能就是 128-11=117 Bytes禾唁。

一般默認使用 OPENSSL_PKCS1_PADDING无切。PHP 支持的 Padding 有 OPENSSL_PKCS1_PADDING哆键、OPENSSL_SSLV23_PADDING籍嘹、OPENSSL_PKCS1_OAEP_PADDING 和 OPENSSL_NO_PADDING。

接收方解密時也需要分組泪掀。將加密后的原始二進制數(shù)據(jù)(對于經(jīng)過 BASE64 的數(shù)據(jù)异赫,需要解碼),每 128 Bytes 分為一組切油,然后再進行解密名惩。解密后,根據(jù) Padding 的長度丟棄隨機字符串稚伍,把得到的原字符串拼接起來戚宦,就得到原始報文受楼。

在非對稱加密系統(tǒng)出現(xiàn)之前艳汽,所有加密和解密使用同樣規(guī)則河狐,這些規(guī)則相當于密鑰,稱為對稱加密算法(Symmetric-key algorithm)栅干。其中又以高級加密標準為代表(英語:Advanced Encryption Standard非驮,縮寫:AES)劫笙,在密碼學(xué)中又稱Rijndael加密法星岗,是美國聯(lián)邦政府采用的一種區(qū)塊加密標準俏橘。這個標準用來替代原先的DES寥掐,已經(jīng)被多方分析且廣為全世界所使用召耘。

密鑰生成

openssl genrsa 用于生成rsa私鑰文件,生成是可以指定私鑰長度剖踊,具體參數(shù)請參考文檔德澈。

openssl genrsa -out 2048_rsa_private_key.pem 2048 

Rsa命令用于處理Rsa密鑰生成公鑰梆造、格式轉(zhuǎn)換和打印信息

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

-in filename:輸入的RSA密鑰文件澳窑,在此為上面生成的密鑰 rsa_private_key.pem摊聋。
-pubout:設(shè)置此選項后,保存公鑰值到輸出文件中箍镜。
-out filename:輸出文件色迂,在此我們定義成rsa_public_key.pem

java 開發(fā)使用的 PKCS8 格式轉(zhuǎn)換命令

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诈悍,一種被廣泛使用的密碼散列函數(shù)侥钳,其中以 MD5消息摘要算法為普遍柄错。
Secure Hash Algorithm 縮寫為SHA售貌,密碼散列函數(shù)颂跨。能計算出一個數(shù)字消息所對應(yīng)到的毫捣,固定長度字符串的算法蔓同,也是消息摘要算法的一種。

這些算法(md,sha)之所以稱作安全算法基于以下兩點:
(1)由消息摘要反推原輸入消息弃揽,從計算理論上來說是很困難的矿微。但目前有人制造出碰撞的可能了涌矢,大大減弱了安全性娜庇。
(2)想要找到兩組不同的消息對應(yīng)到相同的消息摘要方篮,從計算理論上來說是很困難的。任何對輸入消息的變動匕得,都會很高概率導(dǎo)致其產(chǎn)生的消息摘要迥異汁掠。

HMAC:散列消息身份驗證碼 Hashed Message Authentication Code 调塌。

根據(jù)RFC 2316羔砾,HMAC以及IPSec被認為是Interact安全的關(guān)鍵性核心協(xié)議姜凄。它不是散列函數(shù)趾访,而是采用了將MD5或SHA1散列函數(shù)與共享機密密鑰(與公鑰/私鑰對不同)一起使用的消息身份驗證機制扼鞋。基本來說淫半,消息與密鑰組合并運行散列函數(shù)科吭。然后運行結(jié)果與密鑰組合并再次運行散列函數(shù)对人。這個128位的結(jié)果被截斷成96位牺弄,成為MAC宜狐。然后創(chuàng)建兩個B長的不同字符串:

innerpad = 長度為B的 0×36
outterpad = 長度為B的 0×5C
計算輸入字符串str的HMAC:
hash(key ^ outterpad, hash(key ^ innerpad, str))

hmac主要應(yīng)用在身份驗證中肌厨,它的使用方法是這樣的:

1. 客戶端發(fā)出登錄請求(假設(shè)是瀏覽器的GET請求)
2. 服務(wù)器返回一個隨機值柑爸,并在會話中記錄這個隨機值
3. 客戶端將該隨機值作為密鑰表鳍,用戶密碼進行hmac運算譬圣,然后提交給服務(wù)器
4. 服務(wù)器讀取用戶數(shù)據(jù)庫中的用戶密碼和步驟2中發(fā)送的隨機值做與客戶端一樣的hmac運算厘熟,然后與用戶發(fā)送的結(jié)果比較绳姨,如果結(jié)果一致則驗證用戶合法

在這個過程中飘庄,可能遭到安全攻擊的是服務(wù)器發(fā)送的隨機值和用戶發(fā)送的hmac結(jié)果跪削,而對于截獲 了這兩個值的黑客而言這兩個值是沒有意義的,絕無獲取用戶密碼的可能性锁摔,隨機值的引入使hmac只在當前會話中有效,大大增強了安全性和實用性涩盾。大多數(shù)的 語言都實現(xiàn)了hmac算法春霍,比如php的mhash址儒、python的hmac.py莲趣、java的MessageDigest類饱溢,在web驗證中使用 hmac也是可行的绩郎,用js進行md5運算的速度也是比較快的肋杖。

PHP與OpenSSL RSA非對稱加解密

RSA使用非對稱加解密字符長度是 密鑰長度/8bit=字節(jié)的長度浊竟,如1024對應(yīng)的數(shù)據(jù)分組長度128字節(jié)逐沙,2048對數(shù)據(jù)分組256字節(jié)洼畅。 RSA加密解密有四個配置的方法帝簇,使用私鑰加密就對應(yīng)公鑰解密,反之公鑰加密就用私鑰解密胧后,配套使用壳快。

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察滑。

exec('chcp 936');

date_default_timezone_set("Asia/Shanghai"); 

class Crypto{

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

    static function keygen(){
        // window系統(tǒng)要設(shè)置openssl環(huán)境變量或通過配置信息指定配置文件
        $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,OPENSSL_PKCS1_PADDING);
            $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,OPENSSL_PKCS1_PADDING);
            $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,OPENSSL_PKCS1_PADDING);
            $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,OPENSSL_PKCS1_PADDING);
            $decrypted .= $decryptedTemp;
        }
        openssl_free_key($key);
        return $decrypted;
    }
    
}

$plain  = "Some secret here for you ...";
$key    = openssl_random_pseudo_bytes(32);

$cipher = Crypto::encrypt($plain, $key);
$msg    = Crypto::decrypt($cipher, $key);
print_r(['明文'=>$plain, '密碼'=>base64_encode($key), '解密'=>$msg, '密文'=>$cipher]);

$plain  = "利用公鑰加密打厘,私鑰解密做數(shù)據(jù)保密通信!";
$cipher = Crypto::publicEncrypt($plain);
// $cipher = "填入Java生成的密文(Base64編碼)以解密";
$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(['預(yù)簽'=>$msg, '簽名'=>$sign, '驗證'=>$verify==1?"PASS":"FAIL"]);

心臟滴血漏洞

Heartbleed漏洞是由安全公司Codenomicon和谷歌安全工程師于2014年4月7號公布的贺辰。主要受影響的是OpenSSL1.0.1版本,Heartbleed漏洞是由于未能在memcpy()調(diào)用受害用戶輸入內(nèi)容作為長度參數(shù)之前正確進行邊界檢查魂爪。攻擊者可以追蹤OpenSSL所分配的64KB緩存先舷、將超出必要范圍的字節(jié)信息復(fù)制到緩存當中再返回緩存內(nèi)容,這樣一來受害者的內(nèi)存內(nèi)容就會以每次64KB的速度進行泄露滓侍。

keytool與Java RSA readme ===============================

阿里支付開發(fā)文檔生成RSA密鑰工具 - https://docs.open.alipay.com/291/106097/
Keytool Manages a keystore (database) - https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html
KeyStore工具類 - https://docs.oracle.com/javase/6/docs/api/java/security/KeyStore.html
KeyPair工具類 - https://docs.oracle.com/javase/7/docs/api/java/security/KeyPair.html
Cipher加密解密工具 - https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
Java&keytool生成RSA密鑰 - https://bijian1013.iteye.com/blog/2339874

生成公鑰和私鑰

生成方法一是使用 Java 提供的工具類 KeyPairGenerator 生成公鑰和私鑰蒋川。另一種是使用第三方工具軟件如 OpenSSl 來生成,使用OpenSSL生成密鑰時撩笆,要轉(zhuǎn)換成 Java 使用的 PKCS8 格式捺球,讀入時使用 PKCS8EncodedKeySpec 工具類即可以導(dǎo)入密鑰。還有JDK自帶的 keytool 這個密鑰和證書管理工具夕冲,它能夠管理自己的公鑰/私鑰對及相關(guān)證書氮兵,可用于自簽認證或數(shù)據(jù)完整性以及認證服務(wù)。在JDK 1.4以后的版本中都包含了這一工具歹鱼,可以在 JAVA_HOME 的 bin 目錄下找到泣栈。如果使用 .net 平臺,還可以使用 makecert 這個密鑰管理工具。

使用 keytool -genkeypair 命令生成密鑰倉庫文件南片,早期版本使用 -genkey掺涛,對于現(xiàn)有倉庫文件,可以使用 keytool -list 來查看內(nèi)容疼进。通過 -keypass 可以指定密碼來加密存儲薪缆,至少6個字符,可以是純數(shù)字或者字母或者數(shù)字和字母的組合等等伞广。所有keystore的條目可以通過一個唯一別名來訪問拣帽,別名設(shè)置通過 -alias 參數(shù)指定。-keyalg 指定加密算法嚼锄,本例中的采用通用的RAS加密算法; -keystore 密鑰庫的路徑及名稱减拭,不指定的話,默認在操作系統(tǒng)的用戶目錄下生成一個".keystore"的文件灾票。庫文件格式默認為 JKS,如果通過 -storetype 指定其它格式茫虽,那后續(xù)使用其它命令時也要相應(yīng)指定相同格式刊苍。

生成倉庫文件后,通過 keyStore 類來讀取私鑰濒析,公鑰可以導(dǎo)出在crt證書保存正什,然后使用證書工廠類 CertificateFactory 進行讀取,也可以通過 KeyStore 工具類在倉庫中提取證書号杏。以下命令生成密碼倉庫及導(dǎo)出證書CRT文件供參考:

keytool -genkey -v -alias Heartbleed -dname "CN=Heartbleed,OU=HE,O=CUI,L=HAIDIAN,ST=BEIJING,C=CN" -keyalg RSA -keysize 1024 -keypass xxxxxx -keystore Heartbleed.store -storepass xxxxxx -validity 10000 -storetype JCEKS
keytool -exportcert -alias Heartbleed -file Heartbleed.crt -keystore Heartbleed.store -storepass xxxxxx -rfc -storetype JCEKS

keytool -list -storetype JCEKS -keystore Heartbleed.store        查看庫里面的所有證書
keytool -export -alias test1 -file test.crt -keystore Heartbleed.store 證書條目導(dǎo)出到證書文件Heartbleedcrt
keytool -import -keystore Heartbleed.store -file Heartbleed.crt  將證書文件Heartbleedcrt導(dǎo)入到倉庫文件
keytool -printcert -file "Heartbleed.crt"   查看證書信息
keytool -delete -keystore Heartbleed.store -alias test1          刪除密鑰庫中的指定別名的條目
keytool -keypasswd -alias test2 -keystore Heartbleed.store       修改證書指定別名條目的口令

密鑰庫文件格式【Keystore】

格式     擴展名    描述及特點
JKS     .jks .ks  【Java Keystore】 SUN提供密鑰庫的Java實現(xiàn)版本        密鑰庫和私鑰用不同的密碼進行保護
JCEKS   .jce      【JCE Keystore】 SUN JCE提供密鑰庫的JCE實現(xiàn)版本      相對于JKS安全級別更高婴氮,保護Keystore私鑰時采用TripleDES
PKCS12  .p12 .pfx 【PKCS #12】 個人信息交換語法標準                    1、包含私鑰盾致、公鑰及其證書 2主经、密鑰庫和私鑰用相同密碼進行保護
BKS     .bks      【Bouncycastle Keystore】 密鑰庫的BC實現(xiàn)版本         基于JCE實現(xiàn)
UBER    .ubr      【Bouncycastle UBER Keystore】 密鑰庫的BC更安全實現(xiàn)版本

證書文件格式【Certificate】

格式     擴展名           描述及特點
DER     .cer .crt .rsa   【ASN .1 DER】用于存放證書           不含私鑰、二進制
PKCS7   .p7b .p7r        【PKCS #7】加密信息語法標準           p7b以樹狀展示證書鏈庭惜,不含私鑰罩驻;p7r為CA對證書請求簽名的回復(fù),只能用于導(dǎo)入护赊。
CMS     .p7c .p7m .p7s   【Cryptographic Message Syntax】    p7c只保存證書惠遏,p7m:signature with enveloped data,p7s:時間戳簽名文件
PEM     .pem             【Printable Encoded Message】       PEM是【Privacy-Enhanced Mail】廣泛運用于密鑰管理骏啰,一般基于base 64編碼节吮。
PKCS10  .p10 .csr        【PKCS #10】公鑰加密標準【Certificate Signing Request】 證書簽名請求文件,ASCII文件判耕,CA簽名后以p7r文件回復(fù)透绩。
SPC     .pvk .spc        【Software Publishing Certificate】 微軟公司特有的雙證書文件格式,經(jīng)常用于代碼簽名,其中pvk用于保存私鑰渺贤,spc用于保存公鑰雏胃。

加密與解密

使用工具類 javax.crypto.Cipher,其初始化方法 init 中指定一個模式常數(shù)來明確是加密或是解密

Cipher.ENCRYPT_MODE 1
Cipher.DECRYPT_MODE 2
Cipher.WRAP_MODE    3
Cipher.UNWRAP_MODE  4

在不同的系統(tǒng)中RSA要實現(xiàn)互通志鞍,必須要有統(tǒng)一的參數(shù)瞭亮,如IV值,PADDING等固棚。

簽名與驗證

java.security.Signature 這個工具類用于簽名與驗證

import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import java.io.File;
import java.io.FileWriter;
import java.io.FileReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

import java.util.HashMap;
import java.util.Map;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import java.util.Base64;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.regex.PatternSyntaxException;

public class coding {

    static final String PATH_STORE = "./keys/Heartbleed.store";
    static final String STORE_ALIAS = "Heartbleed";
    static final String STORE_TYPE = "JCEKS";
    static final String STORE_PASS = "xxxxxx";
    static final String PATH_PRIVATE_KEY = "./keys/2048_private_key_pkcs8.pem";
    static final String PATH_PUBLIC_KEY  = "./keys/2048_public_key.pem";
    static final String PATH_CERTIFICATE = "./keys/Heartbleed.crt" ; // KeyTool導(dǎo)出的證書文件

    static public void main(String args[]) throws Exception {
        // keygen();
        // exportKeysFromStore();
        // 字符串定長拆分
        // log( String.join("|","abcdefghijklm".split("(?=(.{3})+(?!.))")) );
        // log( String.join("|","abcdefghijklm".split("(?<=\\G.{4})(?!$)")) );
        test();
    }

    static public void test() throws Exception {
        PrivateKey privateKey = getPrivateKey();
        PublicKey  publicKey  = getPublicKey();
        byte[] bytePrivate = privateKey.getEncoded();
        byte[] bytePublic = publicKey.getEncoded();
        String strPrivate = Base64.getEncoder().encodeToString(bytePrivate);
        String strPublic = Base64.getEncoder().encodeToString(bytePublic);
        // log("私鑰("+ privateKey.getFormat() + ")\r\n" + strPrivate);
        // log("公鑰("+ publicKey.getFormat() + ")\r\n" + strPublic );
        
        String content = "利用公鑰加密统翩,私鑰解密做數(shù)據(jù)保密通信!";
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 準備公鑰加密
        byte[] result = cipher.doFinal(content.getBytes());
        String cipherText = Base64.getEncoder().encodeToString(result);
        log("密文:" + cipherText );
        // result = Base64.getDecoder().decode("填入PHP生成的密文(Base64編碼)以解密");
        cipher.init(Cipher.DECRYPT_MODE, privateKey); // 準備私鑰解密
        byte[] msg = cipher.doFinal(result);
        log("解密:" + new String(msg));
        log("原文:" + content);
        
        // 簽名與驗證
        String signature = sign(content);
        boolean isPass = verify(content, signature);
        log("\n預(yù)簽:"+content+"\n簽名:"+signature + "\n驗證:"+(isPass?"PASS":"FAIL") );

        // AES對稱加密解密
        KeyGenerator aes = KeyGenerator.getInstance("aes");
        SecretKey key = aes.generateKey();
        Cipher aesCipher = Cipher.getInstance("aes");

        aesCipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] aesResult = aesCipher.doFinal(content.getBytes());
        log("\nAES 加密: " + new String(Base64.getEncoder().encodeToString(aesResult)) );
        aesCipher.init(Cipher.DECRYPT_MODE, key);
        aesResult = aesCipher.doFinal(aesResult);
        log("AES 解密: " + new String(aesResult) );
    }

    public static String sign(String plainText) throws Exception {
        try {
            Signature signet = Signature.getInstance("SHA256withRSA");
            signet.initSign(getPrivateKey());
            signet.update(plainText.getBytes());
            return Base64.getEncoder().encodeToString(signet.sign());
        } catch (Exception e) {
            throw e; 
        }
    }

    public static boolean verify(String plainText, String signText) {
        try {
            byte[] signature = Base64.getDecoder().decode(signText);
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initVerify(getPublicKey());
            sign.update(plainText.getBytes());
            return sign.verify(signature);
        } catch (Throwable e) {
            return false;
        }
    }

    private static void exportKeysFromStore() throws Exception {
        KeyPair keys = getKeyPairFromStore();
        PrivateKey privateKey = keys.getPrivate();
        PublicKey publicKey = keys.getPublic();
        byte[] bytePrivate = privateKey.getEncoded();
        byte[] bytePublic = publicKey.getEncoded();
        String base64Private = Base64.getEncoder().encodeToString(bytePrivate);
        String base64Public = Base64.getEncoder().encodeToString(bytePublic);
        base64Private = String.join("\r\n",base64Private.split("(?<=\\G.{64})(?!$)"));
        base64Public = String.join("\r\n",base64Public.split("(?<=\\G.{64})(?!$)"));
        log("私鑰("+ privateKey.getFormat() +")\r\n" + base64Private);
        log("公鑰("+ publicKey.getFormat() + ")\r\n" + base64Public );
        writeToFile(PATH_PRIVATE_KEY, base64Private, "-----BEGIN PRIVATE KEY-----\n", "-----END PRIVATE KEY-----");
        writeToFile(PATH_PUBLIC_KEY, base64Public, "-----BEGIN PUBLIC KEY-----\n", "-----END PUBLIC KEY-----");
    }

    private static KeyPair getKeyPairFromStore() throws Exception {
        char[] password = STORE_PASS.toCharArray();
        String storeType = "".equals(STORE_TYPE) ? KeyStore.getDefaultType() : STORE_TYPE;
        KeyStore keyStore = KeyStore.getInstance(storeType);
        InputStream file = new FileInputStream(PATH_STORE);
        keyStore.load(file, password);
        Key key = keyStore.getKey(STORE_ALIAS,password);
        if(key instanceof PrivateKey) {
            Certificate cert = keyStore.getCertificate(STORE_ALIAS);
            PublicKey publicKey = cert.getPublicKey();
            return new KeyPair(publicKey,(PrivateKey)key);
        }
        return null;
    }

    /**
     * 讀取base64編碼的公鑰文件并構(gòu)造 PKCS#8 格式的私鑰
     * @return PublicKey
     */
    public static PrivateKey getPrivateKey() throws Exception{
        String text = readFile(PATH_PRIVATE_KEY);
        text = text.replaceAll("\r|\n","").replace("-----BEGIN PRIVATE KEY-----","").replace("-----END PRIVATE KEY-----","");
        byte[] data = Base64.getDecoder().decode(text);
        PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(data);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        PrivateKey key = factory.generatePrivate(pkcs8);
        return key;
    }

    /**
     * 讀取base64編碼的公鑰文件并構(gòu)造X509EncodedKeySpec格式的公鑰
     * @return PublicKey
     */
    public static PublicKey getPublicKey() throws Exception{
        String text = readFile(PATH_PUBLIC_KEY);
        text = text.replaceAll("\r|\n","").replace("-----BEGIN PUBLIC KEY-----","").replace("-----END PUBLIC KEY-----","");
        byte[] data = Base64.getDecoder().decode(text);
        X509EncodedKeySpec x509 = new X509EncodedKeySpec(data);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        PublicKey key = factory.generatePublic(x509);
        return key;
    }

    private static PublicKey getPublicKeyFromCrt() throws CertificateException, FileNotFoundException {
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        FileInputStream file = new FileInputStream(PATH_CERTIFICATE);
        Certificate crt = factory.generateCertificate(file);
        PublicKey publicKey = crt.getPublicKey();
        return publicKey;
    }

    private static PrivateKey getPrivateKeyFromStore() throws Exception {  
        String storeType = "".equals(STORE_TYPE) ? KeyStore.getDefaultType() : STORE_TYPE; ;
        char[] pw = STORE_PASS.toCharArray();
        KeyStore keyStore = KeyStore.getInstance(storeType);  
        InputStream file = new FileInputStream(PATH_STORE);  
        keyStore.load(file, pw);  
        // 由密鑰庫獲取密鑰的兩種方式  
        // KeyStore.PrivateKeyEntry pkEntry = keyStore.getEntry(STORE_ALIAS, new KeyStore.PasswordProtection(pw));  
        // return pkEntry.getPrivateKey();  
        return (PrivateKey) keyStore.getKey(STORE_ALIAS, pw);  
    }

    public static void keygen() throws Exception{
        File k1 =new File(PATH_PRIVATE_KEY);    
        File k2 =new File(PATH_PUBLIC_KEY);    
        if( k1.exists() && k2.exists() && !k1.isDirectory()){
            log("Key file exists and return now...");
            return;
        }
        KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
        keygen.initialize(2048);
        KeyPair keyPair = keygen.generateKeyPair();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        byte[] bytePrivate = privateKey.getEncoded();
        byte[] bytePublic = publicKey.getEncoded();
        String base64Private = Base64.getEncoder().encodeToString(bytePrivate);
        String base64Public = Base64.getEncoder().encodeToString(bytePublic);
        base64Private = String.join("\r\n",base64Private.split("(?<=\\G.{64})(?!$)"));
        base64Public = String.join("\r\n",base64Public.split("(?<=\\G.{64})(?!$)"));
        writeToFile(PATH_PRIVATE_KEY, base64Private, "-----BEGIN PRIVATE KEY-----\n", "-----END PRIVATE KEY-----");
        writeToFile(PATH_PUBLIC_KEY, base64Public, "-----BEGIN PUBLIC KEY-----\n", "-----END PUBLIC KEY-----");
        log("私鑰("+ privateKey.getFormat() +")\r\n" + base64Private);
        log("公鑰("+ publicKey.getFormat() + ")\r\n" + base64Public );
        // hibernate(bytePrivate, PATH_PRIVATE_KEY);
        // hibernate(bytePrivate, PATH_PUBLIC_KEY);
    }

    private static void writeToFile(String path, String data, String header, String footer) throws Exception {
        FileWriter fw = new FileWriter(path);
        fw.write(header);
        fw.write(data);
        fw.write("\n");
        fw.write(footer);
        fw.close();
    }

    private static String readFile(String path) throws Exception{
        FileReader file = new FileReader(path);
        char[] buffer = new char[1024*1024];
        int size = file.read(buffer, 0, 1024*1024);
        // while( file.read(buffer, 0, 1024) )...
        return new String(buffer,0,size);
    }

    private static void hibernate(Object key, String path) throws Exception {
        try{
            FileOutputStream fo = new FileOutputStream(path);
            ObjectOutputStream oo = new ObjectOutputStream(fo);
            oo.writeObject(key);
            oo.flush();
            oo.close();
        }catch(Exception e){
            log(e.getMessage());
        }finally{
        }
    }

    private static Key restore(String path) throws Exception {
        try{
            FileInputStream fi = new FileInputStream(path);
            ObjectInputStream oi = new ObjectInputStream(fi);
            Key key = (Key)oi.readObject();
            oi.close();
            return key;
        }catch( Exception e){
            log(e.getMessage());
        }finally{
            return null;
        }
    }

    static public void log(String t){
        System.out.println(t);
    }
    
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市此洲,隨后出現(xiàn)的幾起案子厂汗,更是在濱河造成了極大的恐慌,老刑警劉巖呜师,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娶桦,死亡現(xiàn)場離奇詭異,居然都是意外死亡汁汗,警方通過查閱死者的電腦和手機衷畦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來知牌,“玉大人祈争,你說我怎么就攤上這事〗谴纾” “怎么了菩混?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扁藕。 經(jīng)常有香客問我沮峡,道長,這世上最難降的妖魔是什么亿柑? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任帖烘,我火速辦了婚禮,結(jié)果婚禮上橄杨,老公的妹妹穿的比我還像新娘秘症。我一直安慰自己,他們只是感情好式矫,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布乡摹。 她就那樣靜靜地躺著,像睡著了一般采转。 火紅的嫁衣襯著肌膚如雪聪廉。 梳的紋絲不亂的頭發(fā)上瞬痘,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音板熊,去河邊找鬼框全。 笑死,一個胖子當著我的面吹牛干签,可吹牛的內(nèi)容都是我干的津辩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼容劳,長吁一口氣:“原來是場噩夢啊……” “哼喘沿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起竭贩,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蚜印,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后留量,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窄赋,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年楼熄,在試婚紗的時候發(fā)現(xiàn)自己被綠了忆绰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡孝赫,死狀恐怖较木,靈堂內(nèi)的尸體忽然破棺而出红符,到底是詐尸還是另有隱情青柄,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布预侯,位于F島的核電站致开,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏萎馅。R本人自食惡果不足惜双戳,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望糜芳。 院中可真熱鬧飒货,春花似錦、人聲如沸峭竣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽皆撩。三九已至扣墩,卻和暖如春哲银,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呻惕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工荆责, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亚脆。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓做院,卻偏偏與公主長得像,于是被迫代替她去往敵國和親型酥。 傳聞我的和親對象是個殘疾皇子慌洪,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容