如何保护我们的Java程序安全?——《我的Java打怪日记》

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 前不久开发的政府项目中,政府邀请的安全测试组提出了明文传输漏洞,于是抽空研究了下Java加解密相关知识,记录在此,以便后面查阅。

前言

前不久开发的政府项目中,政府邀请的安全测试组提出了明文传输漏洞,于是抽空研究了下Java加解密相关知识,记录在此,以便后面查阅。

我也了解到,在Java后端接口开发中,涉及到用户私密信息(用户名、密码)等,我们不能传输明文,必须使用加密方式传输。

散列函数

Java提供了一个名为MessageDigest的类,它属于java.security包。 此类支持诸如SHA-1SHA 256MD5之类的算法,以将任意长度的消息转换为信息摘要。

散列函数返回的值称为信息摘要或简称散列值。 下图说明了散列函数。

要使用散列函数加密数据,我们通常按照以下步骤执行:

创建MessageDigest对象

MessageDigest md = MessageDigest.getInstance("MD5");
MessageDigest提供了 getInstance静态方法来获得 MessageDigest实例,支持的类型可参考 Wiki-SHA家族

将数据传递给创建的MessageDigest对象

md.update("gcdd1993".getBytes());

生成消息摘要

byte[] digest = md.digest();

通常我们会将其转换为Hex字符串

StringBuffer hexString = new StringBuffer();

for (byte aDigest : digest) {
    hexString.append(Integer.toHexString(0xFF & aDigest));
}
System.out.println("Hex format : " + hexString.toString());

消息认证码

MAC(消息认证码)算法是一种对称密钥加密技术,用于提供消息认证。要建立MAC过程,发送方和接收方共享对称密钥K。

实质上,MAC是在基础消息上生成的加密校验和,它与消息一起发送以确保消息验证。

使用MAC进行身份验证的过程如下图所示

在Java中,javax.crypto包的Mac类提供了消息认证代码的功能。按照以下步骤使用此类创建消息身份验证代码。

创建KeyGenerator对象

KeyGenerator keyGen = KeyGenerator.getInstance("DES");

KeyGenerator支持以下类型:

  • AES (128)
  • DES (56)
  • DESede (168)
  • HmacSHA1
  • HmacSHA256

创建SecureRandom对象

SecureRandom secureRandom = new SecureRandom();

初始化KeyGenerator

keyGen.init(secureRandom);

生成密钥

Key key = keyGen.generateKey();

使用密钥初始化Mac对象

Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);

Mac支持以下类型:

  • HmacMD5
  • HmacSHA1
  • HmacSHA256

完成mac操作

String msg = "gcdd1993";
byte[] bytes = msg.getBytes();
byte[] macResult = mac.doFinal(bytes);

数字签名

数字签名允许验证签名的作者,日期和时间,验证消息内容。 它还包括用于其他功能的身份验证功能。

优点

  • 认证

    数字签名有助于验证消息来源。
  • 完整性

    邮件签名后,邮件中的任何更改都将使签名无效。
  • 不可否认

    通过此属性,任何已签署某些信息的实体都不能在以后拒绝签名。

创建数字签名

创建KeyPairGenerator对象

KeyPairGenerator类提供 getInstance()方法,该方法接受表示所需密钥生成算法的String变量,并返回生成密钥的 KeyPairGenerator对象。
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DSA");

初始化KeyPairGenerator对象

KeyPairGenerator类提供了一个名为 initialize()的方法,该方法用于初始化密钥对生成器。 此方法接受表示密钥大小的整数值。
keyPairGen.initialize(2048);

生成KeyPair

使用 generateKeyPair()方法生成密钥对
KeyPair pair = keyPairGen.generateKeyPair();

从密钥对中获取私钥

PrivateKey privateKey = pair.getPrivate();

创建签名对象

Signature类的getInstance()方法接受表示所需签名算法的字符串参数,并返回相应的Signature对象。

Signature支持以下类型:

  • SHA1withDSA
  • SHA1withRSA
  • SHA256withRSA
Signature sign = Signature.getInstance("SHA256withDSA");

初始化签名对象

sign.initSign(privateKey);

将数据添加到Signature对象

String msg = "gcdd1993";
sign.update(msg.getBytes());

计算签名

byte[] signature = sign.sign();

验证签名

我们创建签名后,通常可以将私钥发送到客户端,以进行签名操作。服务端保存公钥,以进行签名验证

初始化签名对象以进行验证

使用公钥初始化签名对象
sign.initVerify(pair.getPublic());

更新要验证的数据

sign.update(msg.getBytes());

验证签名

boolean verify = sign.verify(signature);
Assert.assertTrue(verify);

公私钥加解密数据

可以使用 javax.crypto包的Cipher类加密给定数据。

获取公私钥的步骤,与签名类似

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(2048);
KeyPair pair = keyPairGen.generateKeyPair();
PublicKey publicKey = pair.getPublic();

加密数据

创建一个Cipher对象

Cipher类的getInstance()方法接受表示所需转换的String变量,并返回实现给定转换的Cipher对象。

Cipher支持以下类型:

  • AES/CBC/NoPadding (128)
  • AES/CBC/PKCS5Padding (128)
  • AES/ECB/NoPadding (128)
  • AES/ECB/PKCS5Padding (128)
  • DES/CBC/NoPadding (56)
  • DES/CBC/PKCS5Padding (56)
  • DES/ECB/NoPadding (56)
  • DES/ECB/PKCS5Padding (56)
  • DESede/CBC/NoPadding (168)
  • DESede/CBC/PKCS5Padding (168)
  • DESede/ECB/NoPadding (168)
  • DESede/ECB/PKCS5Padding (168)
  • RSA/ECB/PKCS1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

使用公钥初始化Cipher对象

Cipher类的 init()方法接受两个参数,一个表示操作模式的整数参数(加密/解密)和一个表示公钥的Key对象。
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

将数据添加到Cipher对象

Cipher类的 update()方法接受表示要加密的数据的字节数组,并使用给定的数据更新当前对象。
String msg = "gcdd1993";
cipher.update(msg.getBytes());

加密数据

byte[] cipherText = cipher.doFinal();

解密数据

使用私钥初始化Cipher对象

cipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());

解密数据

byte[] decipheredText = cipher.doFinal(cipherText);
Assert.assertEquals(msg, new String(decipheredText));

第三方类库

前后端适用且应用广泛的是[ Crypto-JS],使用 Crypto-JS 可以非常方便地在 JavaScript 进行 MD5SHA1SHA2SHA3RIPEMD-160 哈希散列,进行 AESDESRabbitRC4Triple DES 加解密。

AES加密

高级加密标准(英语: Advanced Encryption Standard,缩写:[AES]),在密码学中又称 Rijndael加密法,是美国联邦政府采用的一种 区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用。

一般来说,我们可以在服务端随机生成密钥,然后将密钥发送给客户端进行加密,上传密文到服务端,服务端进行解密。

本文只讨论JavaAES加解密方式。

引入Jar包

compile group: 'org.webjars.npm', name: 'crypto-js', version: '3.1.8'

生成密钥

Random random = new Random();
byte[] key = new byte[16];
random.nextBytes(key);
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");

生成偏移量

byte[] iv = new byte[16];
random.nextBytes(iv);
IvParameterSpec ivSpec = new IvParameterSpec(iv);

创建Cipher对象

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

初始化Cipher为加密工作过程

cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

加密

byte[] original = cipher.doFinal(encrypted1);

AES解密

初始化Cipher为解密工作过程

cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);

解密

byte[] bytes = cipher.doFinal(original);
Assert.assertEquals(data, new String(bytes, StandardCharsets.UTF_8));

AES加解密总结

实际项目中,可以按照以下方式实现对称加密

  1. 服务端提供一个接口,该接口负责随机生成key(密码)和iv(偏移量),并将其存入redis(设置超时时间)
  2. 客户端调用接口,获得key和iv以及一个redis_key,进行数据加密,将加密后的数据以及redis_key传到服务端
  3. 服务端使用redis_key获得key和iv,进行解密

总结

Java EE安全里,主要是进行客户端加密,以及服务端解密的过程来实现数据安全传输的目的。在这个过程中,特别要注意以下几点:

  • 随机性:加密方式不可单一,可通过更换Cipher.getInstance()的String值来随机生成加密工人进行加密。
  • 保密性:加密使用的密钥或者偏移量等,需要使用超时、模糊目的等手段进行隐藏,加大破解成本。

没有完全有效的加密,但是只要做到破解成本大于加密成本,就是有效的加密。这样,我们可以不断地更换加密方式达到我们想要的效果。

👉 文中所有的示例代码可以在这里找到:https://github.com/gcdd1993/java-security-sample

相关文章
|
2月前
|
Java 流计算
利用java8 的 CompletableFuture 优化 Flink 程序
本文探讨了Flink使用avatorscript脚本语言时遇到的性能瓶颈,并通过CompletableFuture优化代码,显著提升了Flink的QPS。文中详细介绍了avatorscript的使用方法,包括自定义函数、从Map中取值、使用Java工具类及AviatorScript函数等,帮助读者更好地理解和应用avatorscript。
利用java8 的 CompletableFuture 优化 Flink 程序
|
3月前
|
XML 存储 JSON
Java程序部署
Java程序部署
|
28天前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
48 4
|
2月前
|
Java Maven 数据安全/隐私保护
如何实现Java打包程序的加密代码混淆,避免被反编译?
【10月更文挑战第15天】如何实现Java打包程序的加密代码混淆,避免被反编译?
84 2
|
2月前
|
安全 Java Linux
java程序设置开机自启
java程序设置开机自启
131 1
|
2月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
53 1
|
2月前
|
安全 Java 编译器
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
|
3月前
|
安全 Java API
java安全特性
java安全特性
29 8
|
3月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
47 5
|
3月前
|
Java 编译器 数据库连接
探索Java中的异常处理:提升程序的鲁棒性
【9月更文挑战第25天】在Java的世界里,异常是那些不请自来、令人头疼的“客人”。它们悄无声息地潜入我们的代码,一旦出现,便可能导致程序崩溃或行为异常。但是,如果能够妥善管理这些异常,我们就能将潜在的灾难转变为增强程序鲁棒性和用户体验的机会。本文将通过深入浅出的方式,带领读者理解Java异常处理的重要性,并提供实用的策略来优雅地处理这些意外情况。让我们一起学习如何在Java中捕捉、处理和预防异常,确保我们的程序即使在面对不可预见的错误时也能保持稳健运行。