PHP+JAVA实现RSA互通加密解密

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

先学习一下汉语,密钥[yuè],这个读音表示开锁或上锁的用具,来一起蜜月......

本篇将前面写的《PHP与OpenSSL工具包 AES+RSA》一并整合,与Java版本的RSA来个联合工作,实现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加密算法是一种非对称加密算法,在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由麻省理工学院的罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起设计的,RSA就是他们三人姓氏开头字母拼在一起组成的。非对称指的是加密解密用的是不同的一组密钥,这就是与对称加密的最大区别。非对称加密算法的实现使得密码可以明文传输而没有泄密风险,基本原理是:

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

RSA 加密或签名后的结果是不可读的二进制,使用时经常会转为 BASE64 码再传输。

RSA 加密时,对要加密数据的大小有限制,最大不大于密钥长度。例如在使用 1024 bit 的密钥时(genrsa -out rsa_private_key.pem 1024),最大可以加密 1024/8=128 Bytes 的数据。数据大于 128 Bytes 时,需要对数据进行分组加密(如果数据超限,加解密时会失败,openssl 函数会返回 false),分组加密后的加密串拼接成一个字符串后发送给客户端。

为了保证每次加密的结果都不同,RSA 加密时会在待加密数据后拼接一个随机字符串,再进行加密。不同的填充方式 Padding 表示这个字符串的不同长度,在对超限数据进行分组后,会按照这个 Padding 指定的长度填入随机字符串。例如如果 Padding 填充方式使用默认的 OPENSSL_PKCS1_PADDING(需要占用 11 个字节用于填充),那么明文长度最多只能就是 128-11=117 Bytes。

一般默认使用 OPENSSL_PKCS1_PADDING。PHP 支持的 Padding 有 OPENSSL_PKCS1_PADDING、OPENSSL_SSLV23_PADDING、OPENSSL_PKCS1_OAEP_PADDING 和 OPENSSL_NO_PADDING。

接收方解密时也需要分组。将加密后的原始二进制数据(对于经过 BASE64 的数据,需要解码),每 128 Bytes 分为一组,然后再进行解密。解密后,根据 Padding 的长度丢弃随机字符串,把得到的原字符串拼接起来,就得到原始报文。

在非对称加密系统出现之前,所有加密和解密使用同样规则,这些规则相当于密钥,称为对称加密算法(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。

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系统要设置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,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  = "利用公钥加密,私钥解密做数据保密通信!";
$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(['预签'=>$msg, '签名'=>$sign, '验证'=>$verify==1?"PASS":"FAIL"]);

心脏滴血漏洞

Heartbleed漏洞是由安全公司Codenomicon和谷歌安全工程师于2014年4月7号公布的。主要受影响的是OpenSSL1.0.1版本,Heartbleed漏洞是由于未能在memcpy()调用受害用户输入内容作为长度参数之前正确进行边界检查。攻击者可以追踪OpenSSL所分配的64KB缓存、将超出必要范围的字节信息复制到缓存当中再返回缓存内容,这样一来受害者的内存内容就会以每次64KB的速度进行泄露。

keytool与Java RSA readme ===============================

阿里支付开发文档生成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生成密钥时,要转换成 Java 使用的 PKCS8 格式,读入时使用 PKCS8EncodedKeySpec 工具类即可以导入密钥。还有JDK自带的 keytool 这个密钥和证书管理工具,它能够管理自己的公钥/私钥对及相关证书,可用于自签认证或数据完整性以及认证服务。在JDK 1.4以后的版本中都包含了这一工具,可以在 JAVA_HOME 的 bin 目录下找到。如果使用 .net 平台,还可以使用 makecert 这个密钥管理工具。

使用 keytool -genkeypair 命令生成密钥仓库文件,早期版本使用 -genkey,对于现有仓库文件,可以使用 keytool -list 来查看内容。通过 -keypass 可以指定密码来加密存储,至少6个字符,可以是纯数字或者字母或者数字和字母的组合等等。所有keystore的条目可以通过一个唯一别名来访问,别名设置通过 -alias 参数指定。-keyalg 指定加密算法,本例中的采用通用的RAS加密算法; -keystore 密钥库的路径及名称,不指定的话,默认在操作系统的用户目录下生成一个".keystore"的文件。库文件格式默认为 JKS,如果通过 -storetype 指定其它格式,那后续使用其它命令时也要相应指定相同格式。

生成仓库文件后,通过 keyStore 类来读取私钥,公钥可以导出在crt证书保存,然后使用证书工厂类 CertificateFactory 进行读取,也可以通过 KeyStore 工具类在仓库中提取证书。以下命令生成密码仓库及导出证书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 证书条目导出到证书文件Heartbleedcrt
keytool -import -keystore Heartbleed.store -file Heartbleed.crt  将证书文件Heartbleedcrt导入到仓库文件
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实现版本        密钥库和私钥用不同的密码进行保护
JCEKS   .jce      【JCE Keystore】 SUN JCE提供密钥库的JCE实现版本      相对于JKS安全级别更高,保护Keystore私钥时采用TripleDES
PKCS12  .p12 .pfx 【PKCS #12】 个人信息交换语法标准                    1、包含私钥、公钥及其证书 2、密钥库和私钥用相同密码进行保护
BKS     .bks      【Bouncycastle Keystore】 密钥库的BC实现版本         基于JCE实现
UBER    .ubr      【Bouncycastle UBER Keystore】 密钥库的BC更安全实现版本

证书文件格式【Certificate】

格式     扩展名           描述及特点
DER     .cer .crt .rsa   【ASN .1 DER】用于存放证书           不含私钥、二进制
PKCS7   .p7b .p7r        【PKCS #7】加密信息语法标准           p7b以树状展示证书链,不含私钥;p7r为CA对证书请求签名的回复,只能用于导入。
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文件回复。
SPC     .pvk .spc        【Software Publishing Certificate】 微软公司特有的双证书文件格式,经常用于代码签名,其中pvk用于保存私钥,spc用于保存公钥。

加密与解密

使用工具类 javax.crypto.Cipher,其初始化方法 init 中指定一个模式常数来明确是加密或是解密

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

在不同的系统中RSA要实现互通,必须要有统一的参数,如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导出的证书文件

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

推荐阅读更多精彩内容