详解Java 加密解密和数字签名问题

在做项目中,只要涉及敏感信息,或者对安全有一定要求的场景,都需要对数据进行加密。在Java中原生API即可实现对称加密与非对称加密,并支持常用的加密算法。

对称加密

对称加密使用单钥完成加解密,加密和解密采用相同的密钥。对称加密的速度快,常用于大量数据进行加密。主流的算法有:AES,3DES。

生成3DES密钥

<em>/**
 * 对称加密-3DES算法,取代旧的DES
 */</em>
SecretKey desKey = KeyGenerator.getInstance("DESede").generateKey();

生成AES密钥

<em>/**
* 生成AES算法的密钥
*/</em>
SecretKey aesKey = KeyGenerator.getInstance("AES").generateKey();

保存密钥

对称密钥一般使用二进制保存

SecretKey desKey = KeyGenerator.getInstance("DESede").generateKey();
<em>// 二进制密钥</em>
byte[] bkey = desKey.getEncoded();
<em>// 转成十六进制</em>
String keyStr = Hex.byteCoverToString(bkey);

读取密钥

可以自己生成随机字符串转成byte数组生成密钥,注意byte长度不能小于24位。

<em>// bkey是byte数组</em>
DESedeKeySpec keySpec = new DESedeKeySpec(bkey);
<em>// 读取AES密钥</em>
SecretKey srtKey = SecretKeyFactory.getInstance("AES").generateSecret(keySpec);

Hex是我自己写的工具类。byte数组和十六进制互转的方法有很多,篇幅有限就不贴出来了。可以引入 Apache Commons Codec 工具类,提供Hex,Base64等常用方法。

非对称加密

非对称加密使用密钥对进行加密。一般使用私密加密,公钥解密,也可以反过来用。但自己加密的数据自己不能解,只能依靠对方解密,可以很好的防止单方面密钥泄露。常用的加密算法有:RSA,DSA。

生成密钥对

<em>/**
 * 可以传RSA或DSA算法
 */</em>
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
<em>// 私钥</em>
PrivateKey prvKey = keyPair.getPrivate();
<em>// 公钥</em>
PublicKey pubKey = keyPair.getPublic();

保存密钥

非对称密钥一般使用Base64编码保存。

<em>// 私钥串</em>
String prvKeyStr = Base64.getEncoder().encodeToString(prvKey.getEncoded());
<em>// 公钥串</em>
String pubKeyStr = Base64.getEncoder().encodeToString(pubKey.getEncoded());

非对称加密对加密内容长度有限制,不能超过192位。虽然可以使用加密数据数流来突破限制,但由于非对称加密效率不如对称加密,非对称加密一般用来加密对称密钥。

读取密钥

<em>// 从二进制中读取私钥</em>
byte[] bPrvKey = Base64.getDecoder().decode(prvKeyStr);
PKCS8EncodedKeySpec rsaKeySpec = new PKCS8EncodedKeySpec(bPrvKey);
PrivateKey prvKey = KeyFactory.getInstance("RSA").generatePrivate(rsaKeySpec);

<em>// 从二进制中读取公钥</em>
byte[] bPubKey = Base64.getDecoder().decode(pubKeyStr);
X509EncodedKeySpec rsaKeySpec1 = new X509EncodedKeySpec(bPubKey);
PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(rsaKeySpec1);

我们在接入其他系统中,有时不是给密钥串,而是一个CA证书或PFX密钥文件。HTTS网站就是使用非对称加密的,网站会把CA证书挂在上面让浏览器下载,和服务器进行加解密交互。

从CA证书中读取公钥

<em>// keyFilePath是证书文件</em>
try (InputStream fin = new FileInputStream(keyFilePath)) {
    CertificateFactory f = CertificateFactory.getInstance("X.509");
    X509Certificate certificate = (X509Certificate) f.generateCertificate(fin);
    PublicKey pubKey = certificate.getPublicKey();
} catch (IOException | CertificateException e) {
    e.printStackTrace();
}

从PFX文件中读取密钥

根据密钥存储类型,下面方可以读取pfx或jks类型的密钥文件。

try (InputStream is = new FileInputStream(keyFilePath)) {
    <em>// pkcs12或jks</em>
    KeyStore store = KeyStore.getInstance("pkcs12");
    <em>// 密码</em>
    store.load(is, "密码".toCharArray());
    <em>// 获取所有密钥别名列表</em>
    Enumeration<String> e = store.aliases();
    <em>// 如果有,读取第一个密钥</em>
    if (e.hasMoreElements()) {
        String alias = e.nextElement();
        <em>// 私钥</em>
        PrivateKey prvKey = (PrivateKey) store.getKey(alias, password.toCharArray());
        <em>// 公钥</em>
        PublicKey pubKey = store.getCertificate(alias).getPublicKey();
        <em>// 创建密钥对</em>
        KeyPair keyPare = new KeyPair(pubKey, prvKey);
    }

} catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
    e.printStackTrace();
}

密钥文件是用来存储密钥的仓库。Java中KeyTool工具可以创建jks密钥文件。jks密钥文件中可以存储多个密钥。如果有多个密钥文件,可以根据别名来读取指定密钥。

加密扩展包——JCE

使用JDK8,读取PFX密钥也许会遇到:java.security.InvalidKeyException:illegal Key Size

这是因为jdk sercurity的jar包限制,只支持128bit的密钥。jar包如下:

$JAVA_HOME/lib/security/local_policy.jar

$JAVA_HOME/jre/lib/security/US_export_policy.jar

到Oracle官网下载JDK对应的加密扩展包(JCE)。以下是JDK8的JCE

https://www.oracle.com/java/technologies/javase-jce8-downloads.html

把下载后的文件解压,找到 local_policy.jar 和 US_export_policy.jar

覆盖jdk目录下, $JAVA_HOME/jre/lib/security 和  $JAVA_HOME/lib/security 下的文件。(有的版本JDK可能没有 $JAVA_HOME/lib/security 目录,则忽略)

再运行代码,就能正常读取256bit的密钥。

加密&解密

用上面生成的密钥(对称/非对密)加密或解密

<em>// 密文</em>
String ciphertext = null;

{
    String plaintext = "加密内容";
    <em>// 密钥算法,RSA/DAS/AESDESed</em>
    Cipher cipher = Cipher.getInstance("RSA");
    <em>// 设置加密模式</em>
    cipher.init(Cipher.ENCRYPT_MODE, prvKey);
    <em>// 加密后的byte数组</em>
    byte[] cipherbyte = cipher.doFinal(plaintext.getBytes());
    <em>// 转成base64</em>
    ciphertext = Base64.getEncoder().encodeToString(cipherbyte);
    logger.info(ciphertext);
}

{
    <em>// 密钥算法,RSA/DAS/AESDESed</em>
    Cipher cipher = Cipher.getInstance("RSA");
    <em>// 设置解密模式</em>
    cipher.init(Cipher.DECRYPT_MODE, pubKey);
    <em>// base64解码获得密文byte数组</em>
    byte[] cipherbyte = Base64.getDecoder().decode(ciphertext);
    <em>// 解密后的byte数组</em>
    byte[] plainbyte = cipher.doFinal(cipherbyte);
    <em>// byte数组转字符串</em>
    String plaintext = new String(plainbyte);
    logger.info(plaintext);
}

签名算法

保证数据安全,仅靠加密还不够。如果密钥被泄漏,攻击者可以伪靠数据,使用密钥加密后给服务器发送信息。所以还需要对数据签名,为安全增加一道屏障。常用的签名算法有:MD5,SHA。非对称加密的公/私钥也可以对数据签名。

MD5或SHA签名

<em>// 可传入MD5或SHA算法</em>
MessageDigest md = MessageDigest.getInstance("MD5");
<em>// 对str数据签名</em>
byte[] bSign = md.digest(str.getBytes());
<em>// 转成十六进制签名串</em>
String signStr = Hex.byteCoverToString(bSign);

公/私密钥签名

<em>/**
 * 签名
 * @param key
 * @param algorithm
 * @param data
 * @return
 */</em>
public static byte[] sign(PrivateKey key, String algorithm, byte[] data) {

    try {

        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(key);
        signature.update(data);
        return signature.sign();

    } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
        throw new CommonException(e);
    }
}

<em>/**
 * 验签
 * @param key
 * @param algorithm
 * @param sign
 * @param data
 * @return
 */</em>
public static boolean verify(PublicKey key, String algorithm, byte[] sign, byte[] data) {

    try {

        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(key);
        signature.update(data);
        return signature.verify(sign);

    } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
        throw new CommonException(e);
    }
}

设计规范示例

示例一

第一次接触Java加密是12年做银联直连支付插件,后台加密规范是银联出的,对称加密与非对称加密都用上了。如下:

协议版本|加密密钥|报文密文|报文签名

协议版本号一般是固定的,如:1.0。

加密密钥是动态生成的对称密钥,每一次请求都会生成,用来加密报文。加密密钥用自己的私钥加密,Base64编码。

报文密文是用动态生成的加密密钥加密后Base64编码。

报文签名是按约将报文明文参数排序,进行MD5计算。

示例二

因工作需内容,接入过几十加支付系统,他们的加触密都大同小异。示例一中银联的那套加密方案后面看到的不多。很多公司用的对称密钥+签名。

报文(key1=value1&k2=value2&sign=value)

先对报文进行规则排序(一般是key的ascii码升序或降序),再用事先约定好的对密密钥(十六进制字符串)拼接在后面,进行MD5计算。

也有的是使用RSA密钥签名验签。

跨语言加解密

Java原生API中加密算法提供者是SUN或Oracle。使用同相的Java代码,用Android加密,后台解密是不通过的。在Android上,算法要用 RSA/ECB/PKCS1Padding ,才能对应Oracle JDK的RSA。主要是因为非对称加密的默认填充方式不一样。

同样,用PHP或.NET语言和Java交互,也会出现不能解密的情况。所以有些系统设计上,只用了签名和对称加密,配合https在传输上保证内容加密。虽然方便,但没实际解决问题。

做Java后台同学可能注意到,引入第三方提供的加解密SDK时,有些会依赖 org.bouncycastle 的包。

这是一个开源项目:Bouncy Castle,支持Java和C#语言。

官网: www.bouncycastle.org

Git: github.com/bcgit/bc-java

BC包需下载对应JDK版本,不同版本的BC包可能会冲突,甚至无法启动工程。

可以使用 Security.addProvider() 手动添加加密提供者。

密钥管理

JDK中有一个 keytool 的工具。可以用来创建密钥库,管理密钥和证书。

keytool创建的密钥库keystore是jks类型的,读取jks密钥库在前面已给出说明了。在tomcat中配置https时是可以直接使用keystore密钥库。nginx或apache使用的还是pfx或pem格式(pem一般只存私钥)。

当我们用Java的Connection连接https有可能会遇到证书错误,是因为证书受信任,或不是受信任的机构颁发的。JDK有一个密钥库用来存放受信任证书。

$JAVA_HOME/jre/lib/security/cacerts

我们可以用keytool管理cacerts,默认密码:changeit

使用keytool在cacerts中添加相应证书,再用Connection访问https就不会报错了。

不建议在cacerts中添加证书,会导至程序可移植性差。在阿里云或Let's Encrypt可以申请免费证书。

到此这篇关于 Java 加密解密和数字签名的文章就介绍到这了,更多相关 Java 加密解密和数字签名内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java加密解密和数字签名完整代码示例

    常见的加密算法 基本的单向加密算法: BASE64严格地说,属于编码格式,而非加密算法 MD5(MessageDigestalgorithm5,信息摘要算法) SHA(SecureHashAlgorithm,安全散列算法) HMAC(HashMessageAuthenticationCode,散列消息鉴别码) 复杂的对称加密(DES.PBE).非对称加密算法: DES(DataEncryptionStandard,数据加密算法) PBE(Password-basedencryption,基于密码

  • Java实现添加、验证PDF数字签名的方法示例

    目录 Jar文件获取及导入方法: 在设置文档内容保护的方法中,除了对文档加密.添加水印外,应用数字签名也是一种有效防伪手段.数字签名的文件比较容易验证,并且具有较高的权威性和可信度.在PDF文档中,有可直接添加或验证数字签名的功能方法,下面的文章中,将通过Java程序来介绍添加数字签名到PDF文档,以及验证签名是否有效. 使用工具:Spire.PDF for Java Jar文件获取及导入方法: 方法1:可通过官网下载jar文件包.下载后,解压文件,并将lib文件夹下的Spire.Pdf.jar

  • JAVA加密算法数字签名实现原理详解

    所谓数字签名就是信息发送者用其私钥对从所传报文中提取出的特征数据(或称数字指纹)进行 RSA 算法操作,以保证发信人无法抵赖曾发过该信息(即不可抵赖性),同时也确保信息报文在经签名后末被篡改(即完整性).当信息接收者收到报文后,就可以用发送者的公钥对数字签名进行验证. 在数字签名中有重要作用的数字指纹是通过一类特殊的散列函数(HASH 函数)生成的,对这些 HASH 函数的特殊要求是: 1:接受的输入报文数据没有长度限制: 2:对任何输入报文数据生成固定长度的摘要(数字指纹)输出 3:从报文能方

  • 详解Java 加密解密和数字签名问题

    在做项目中,只要涉及敏感信息,或者对安全有一定要求的场景,都需要对数据进行加密.在Java中原生API即可实现对称加密与非对称加密,并支持常用的加密算法. 对称加密 对称加密使用单钥完成加解密,加密和解密采用相同的密钥.对称加密的速度快,常用于大量数据进行加密.主流的算法有:AES,3DES. 生成3DES密钥 <em>/** * 对称加密-3DES算法,取代旧的DES */</em> SecretKey desKey = KeyGenerator.getInstance(&quo

  • 详解 PHP加密解密字符串函数附源码下载

    项目中有时我们需要使用PHP将特定的信息进行加密,也就是通过加密算法生成一个加密字符串,这个加密后的字符串可以通过解密算法进行解密,便于程序对解密后的信息进行处理. 下面先给大家展示下效果图,感兴趣的朋友继续阅读全文. 效果演示     源码下载 笔者收录了一些比较经典的PHP加密解密函数代码,分享给大家.加密解密原理一般都是通过一定的加密解密算法,将密钥加入到算法中,最终得到加密解密结果. 1.非常给力的authcode加密函数,Discuz!经典代码(带详解): function authc

  • Java 详解单向加密--MD5、SHA和HMAC及简单实现实例

    Java 详解单向加密--MD5.SHA和HMAC及简单实现实例 概要: MD5.SHA.HMAC这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法. MD5 MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致.MD5是输入不定长度信息,输出固定长度128-bits的算法. MD5算法具有以下特点: 1.压缩性:任意长度的数据,算出的MD5值长度都是固定的. 2.容易计算:从原数据计算出MD5值很容易. 3.抗修改性:对原数据进行任何

  • 详解Java MD5二次加密的应用

    MD5二次加密的应用 当前端传送密码到后端时候,需要进行两次MD5加密,登录和注册时的加解密流程是怎么样的? 前端和后端加密都可以规定使用密码的某几位作为盐进行加解密操作,而这种约定俗成的盐选取操作只有程序员自己知道,所以安全性较高,不需要前后端传送盐. 或者是前端和后端开发人员在开发的时候商量好这个第一层加密的盐,分别在前端和后端存储起来,这样前端在每次发送密码的时候都使用md5配合盐进行加密,服务器因为知道盐,所以可以自然的解密出来. 答: 无论是注册还是登录,密码的第一次md5加密是在前端

  • 详解Java的类加载机制及热部署的原理

    一.什么是类加载 类的加载指的是将类的.class文件的二进制数据读入到内存中,将其放在运行数据区的方法去,然后再堆区创建一个java.lang.Class对象,用来封装类在方法区的数据结构.类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口. 类加载器并不需要等到某个类被"首次主动使用"时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.cla

  • 详解Java网络编程

    一.网络编程 1.1.概述 1.计算机网络是通过传输介质.通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统.网络编程就就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输.Java语言对网络编程提供了良好的支持,通过其提供的接口我们可以很方便地进行网络编程. 2.Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序. 3.Java提供的网络类库,可以实现无痛的网络连接,联

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • Java 加密解密基础分类及模式归纳整理

    Java  加密解密基础: 密码学是研究编制密码和破译密码的技术科学.研究密码变化的客观规律,应用于编制密码以保守通信秘密的,称为编码学:应用于破译密码以获取通信情报的,称为破译学,总称密码学. 密码学常用术语 明文: 待加密数据. 密文: 明文经过加密后数据. 加密: 将明文转换为密文的过程. 加密算法: 将明文转换为密文的转换算法. 加密密钥: 通过加密算法进行加密操作的密钥. 解密: 将密文转换为铭文的过程. 解密算法: 将密文转换为明文的转换算法. 解密密钥: 通过解密短发进行解密操作的

  • 详解Java如何进行Base64的编码(Encode)与解码(Decode)

    关于base64编码Encode和Decode编码的几种方式 Base64是一种能将任意Binary资料用64种字元组合成字串的方法,而这个Binary资料和字串资料彼此之间是可以互相转换的,十分方便.在实际应用上,Base64除了能将Binary资料可视化之外,也常用来表示字串加密过后的内容.如果要使用Java 程式语言来实作Base64的编码与解码功能,可以参考本篇文章的作法. 早期作法 早期在Java上做Base64的编码与解码,会使用到JDK里sun.misc套件下的BASE64Enco

随机推荐