Administrator
发布于 2023-07-16 / 34 阅读
0
0

Cipher Best Practise

refer to : https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html

https://cloud.tencent.com/developer/article/1650103

javax.crypto.Cipher,翻译为密码,其实叫做密码器更加合适。Cipher是JCA(Java Cryptographic Extension,Java加密扩展)的核心,提供基于多种加解密算法的加解密功能。

Cipher初始化transformation(转换模式)的一些知识补充

转换模式transformation一般由三个部分组成,格式是:算法/工作模式/填充模式(algorithm/mode/padding)。例如:DES/CBC/PKCS5Padding。

算法

算法就是指具体加解密算法的名称英文字符串,例如"SHA-256"、"RSA"等,这里不对具体算法的实现原理做具体展开。

工作模式

工作模式其实主要是针对分组密码。分组密码是将明文消息编码表示后的数字(简称明文数字)序列,划分成长度为n的组(可看成长度为n的矢量),每组分别在密钥的控制下变换成等长的输出数字(简称密文数字)序列。工作模式的出现主要基于下面原因:

当需要加密的明文长度十分大(例如文件内容),由于硬件或者性能原因需要分组加密。
多次使用相同的密钥对多个分组加密,会引发许多安全问题。
从本质上讲,工作模式是一项增强密码算法或者使算法适应具体应用的技术,例如将分组密码应用于数据块组成的序列或者数据流。目前主要包括下面五种由NIST定义的工作模式:

模式 名称 描述 典型应用
电子密码本(ECB) Electronic CodeBook 用相同的密钥分别对明文分组独立加密 单个数据的安全传输(例如一个加密密钥)
密码分组链接(CBC) Cipher Block Chaining 加密算法的输入是上一个密文组合下一个明文组的异或 面向分组的通用传输或者认证
密文反馈(CFB) Cipher FeedBack 一次处理s位,上一块密文作为加密算法的输入,产生的伪随机数输出与明文异或作为下一单元的密文 面向分组的通用传输或者认证
输出反馈(OFB) Output FeedBack 与CFB类似,只是加密算法的输入是上一次加密的输出,并且使用整个分组 噪声信道上的数据流的传输(如卫星通信)
计数器(CTR) Counter 每个明文分组都与一个经过加密的计数器相异或。对每个后续分组计数器递增 面向分组的通用传输或者用于高速需求

填充模式

Padding指的是:块加密算法要求原文数据长度为固定块大小的整数倍,如果原文数据长度大于固定块大小,则需要在固定块填充数据直到整个块的数据是完整的。例如我们约定块的长度为128,但是需要加密的原文长度为129,那么需要分成两个加密块,第二个加密块需要填充127长度的数据,填充模式决定怎么填充数据。

对数据在加密时进行填充、解密时去除填充则是通信双方需要重点考虑的因素。对原文进行填充,主要基于以下原因:

  • 首先,考虑安全性。由于对原始数据进行了填充,使原文能够“伪装”在填充后的数据中,使得攻击者很难找到真正的原文位置。

  • 其次,由于块加密算法要求原文数据长度为固定块大小的整数倍,如果加密原文不满足这个条件,则需要在加密前填充原文数据至固定块大小的整数倍。

  • 另外,填充也为发送方与接收方提供了一种标准的形式以约束加密原文的大小。只有加解密双方知道填充方式,才可知道如何准确移去填充的数据并进行解密。

这个填充模式,非常重要,后面会重点说明

init方法

init()方法一共有八个变体方法,此方法主要用于初始化Cipher。

//额外参数是Key(密钥)
public final void init(int opmode,
                       Key key)
                throws InvalidKeyException

//额外参数是Key(密钥)和SecureRandom(随机源)
public final void init(int opmode,
                       Key key,
                       SecureRandom random)
                throws InvalidKeyException

//额外参数是Key(密钥)和AlgorithmParameterSpec(算法参数透明定义)
public final void init(int opmode,
                       Key key,
                       AlgorithmParameterSpec params)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException 

//额外参数是Key(密钥)、AlgorithmParameterSpec(算法参数透明定义)和SecureRandom(随机源)
public final void init(int opmode,
                       Key key,
                       AlgorithmParameterSpec params,
                       SecureRandom random)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//额外参数是Key(密钥)、AlgorithmParameters(算法参数)
public final void init(int opmode,
                       Key key,
                       AlgorithmParameters params)
                throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//额外参数是Key(密钥)、AlgorithmParameters(算法参数)、SecureRandom(随机源)
public final void init(int opmode,
                       Key key,
                       AlgorithmParameters params,
                       SecureRandom random)
                    throws InvalidKeyException,
                       InvalidAlgorithmParameterException

//额外参数是Certificate(证书)
public final void init(int opmode,
                       Certificate certificate)
                throws InvalidKeyException

//额外参数是Certificate(证书)、SecureRandom(随机源)
public final void init(int opmode,
                       Certificate certificate,
                       SecureRandom random)
                throws InvalidKeyException

我们先看第一个init方法:

//额外参数是Key(密钥)
public final void init(int opmode,
                       Key key)
                throws InvalidKeyException

首先opmode就是我们上面说的工作模式,可选的值如下:

Modifier and Type Field and Description
static int DECRYPT_MODEConstant used to initialize cipher to decryption mode.
static int ENCRYPT_MODEConstant used to initialize cipher to encryption mode.
static int PRIVATE_KEYConstant used to indicate the to-be-unwrapped key is a “private key”.
static int PUBLIC_KEYConstant used to indicate the to-be-unwrapped key is a “public key”.
static int SECRET_KEYConstant used to indicate the to-be-unwrapped key is a “secret key”.
static int UNWRAP_MODEConstant used to initialize cipher to key-unwrapping mode.
static int WRAP_MODEConstant used to initialize cipher to key-wrapping mode.

当我们准备对密钥进行加密、解密时,初始化时,需要指定WRAP_MODE、UNWRAP_MODE;表示,cipher处于key-wrapping mode. 或者 key-unwrapping mode.

当我们,传入一个原始的明文密钥,准备对这个明文密钥,进行加密得到一个密文的密钥时,直接调用wrap方法即可;

但是如果传入一个密文的密钥,想要解密,得到一个明文密钥,此时,调用unwrap方法,还需要指定工作模式,因为这个密文的密钥可能是使用对称加密的,也有可能是非对称加密的。如果是对称加密,那么调用unwrap方法需要指定mode为SECRET_KEY;如果是非对称加密,那么调用unwrap方法需要指定mode为PUBLIC_KEY 或者 PRIVATE_KEY

如果是根据密钥,对一段数据进行加密、解密,此时需要指定mode为ENCRYPT_MODEDECRYPT_MODE



上面说完了opmode,接下来,再继续说下Key key

这个Key key其实是为了,将我们的明文密钥,标准化成加密算法需要的格式和长度。
比如,AES要求密钥的长度为128,但是,我们明文的密钥的值为passwrod,这个明显达不到128的要求,因此,就可以借用KeyGenerator来生成一个128的key。

同样的,在解密时,我们也需要将明文密钥,标准化成解密算法需要的格式和长度。然后,才能调用解密算法,对密文进行解密。



接下来,我们再看下另一个init方法:

//额外参数是Key(密钥)和SecureRandom(随机源)
public final void init(int opmode,
                       Key key,
                       SecureRandom random)
                throws InvalidKeyException

相比于上一个init方法,这个init多了一个SecureRandom参数。SecureRandom是随机源,因为有些算法需要每次加密结果都不相同,这个时候需要依赖系统或者传入的随机源,一些要求每次加解密结果相同的算法如AES不能使用此参数

wrap方法和unwrap方法

其实wrap和unwrap是一个互逆的操作:

wrap方法的作用是把原始的密钥通过某种加密算法包装为加密后的密钥,这样就可以避免在传递密钥的时候泄漏了密钥的明文。同时,因为是包装,所以输入是一个SecretKeySpec,返回的是一个字节数组

unwrap方法的作用是把包装(加密)后的密钥解包装为原始的密钥,得到密钥的明文。因为是解包装,所以输入是一个字节数组,输出是一个SecretKeySpec

public final byte[] wrap(Key key)
                  throws IllegalBlockSizeException,
                         InvalidKeyException
                         
                         
public final Key unwrap(byte[] wrappedKey,
                        String wrappedKeyAlgorithm,
                        int wrappedKeyType)
                 throws InvalidKeyException,
                        NoSuchAlgorithmException

下面,看一个示例:

package com.wp.crypto;

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class TestEncrypt {

    private static final String SECRECT = "passwrod";

    public String wrap(String keyString) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //初始化密钥生成器,指定密钥长度为128,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成加密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        // 通过secretKeySpec,来指定填充模式
        cipher.init(Cipher.WRAP_MODE, secretKeySpec);

        SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
        byte[] bytes = cipher.wrap(key);
        // 将字节数组,转为16进制字符串
        return Hex.encodeHexString(bytes);
    }

    public String unwrap(String keyString) throws Exception {
        byte[] rawKey = Hex.decodeHex(keyString);
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //初始化密钥生成器,指定密钥长度为128,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成解密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");

        cipher.init(Cipher.UNWRAP_MODE, secretKeySpec);

        // 解密
        SecretKey key = (SecretKey) cipher.unwrap(rawKey, "AES", Cipher.SECRET_KEY);
        return new String(key.getEncoded());
    }

    public static void main(String[] args) throws Exception {
        TestEncrypt testEncrypt = new TestEncrypt();
        String wrapKey = testEncrypt.wrap("doge");
        System.out.println(wrapKey);
        System.out.println(testEncrypt.unwrap(wrapKey));
    }
}

输出

77050742188d4b97a1d401db902b864d
doge

update

update方法主要用于部分加密或者部分解密,至于加密或是解密取决于Cipher初始化时候的opmode。

即使它有多个变体,但是套路是一样的:依赖于一个输入的缓冲区(带有需要被加密或者被解密的数据)、返回值或者参数是一个输出的缓冲区,一些额外的参数可以通过偏移量和长度控制加密或者解密操作的数据段。

部分加密或者解密操作完毕后,必须要调用Cipher#doFinal()方法来结束加密或者解密操作。

这个update方法和 上面的wrap方法的区别,是什么呢?

img

doFinal方法

/**
 * 结束多部分加密或者解密操作。
 * 此方法需要在update调用链执行完毕之后调用,返回的结果是加密或者解密结果的一部分。
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final byte[] doFinal()
                     throws IllegalBlockSizeException,
                            BadPaddingException

/**
 * 结束多部分加密或者解密操作。
 * 此方法需要在update调用链执行完毕之后调用,传入的output作为缓冲区接收加密或者解密结果的一部分。
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final int doFinal(byte[] output,
                         int outputOffset)
                  throws IllegalBlockSizeException,
                         ShortBufferException,
                         BadPaddingException                         

/**
 * 结束单部分加密或者解密操作。
 * 此方法接收需要加密或者解密的完整报文,返回处理结果
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final byte[] doFinal(byte[] input)
                     throws IllegalBlockSizeException,
                            BadPaddingException

/**
 * 结束单部分或者多部分加密或者解密操作。
 * 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final byte[] doFinal(byte[] input,
                            int inputOffset,
                            int inputLen)
                     throws IllegalBlockSizeException,
                            BadPaddingException      

/**
 * 结束单部分或者多部分加密或者解密操作。
 * 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度,output用于接收加解密的结果
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final int doFinal(byte[] input,
                         int inputOffset,
                         int inputLen,
                         byte[] output)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException                                                         

/**
 * 结束单部分或者多部分加密或者解密操作。
 * 参数inputOffset为需要加解密的报文byte数组的起始位置,inputLen为需要加密或者解密的字节长度,
 * output用于接收加解密的结果,outputOffset用于设置output的起始位置
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final int doFinal(byte[] input,
                         int inputOffset,
                         int inputLen,
                         byte[] output,
                         int outputOffset)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException 
/**
 * 结束单部分或者多部分加密或者解密操作。
 * 参数input为输入缓冲区,output为输出缓冲区
 * 此方法正常调用结束之后Cipher会重置为初始化状态。
 */
public final int doFinal(ByteBuffer input,
                         ByteBuffer output)
                  throws ShortBufferException,
                         IllegalBlockSizeException,
                         BadPaddingException             

doFinal()主要功能是结束单部分或者多部分加密或者解密操作。单部分加密或者解密适用于需要处理的报文长度较短无需分块的情况,这个时候直接使用byte[] doFinal(byte[] input)方法即可。

多部分加密或者解密适用于需要处理的报文长度长度较大,需要进行分块的情况,这个时候需要调用多次update方法变体进行部分块的加解密,最后调用doFinal方法变体进行部分加解密操作的结束。

举个例子,例如处理块的大小为8,实际需要加密的报文长度为23,那么需要分三块进行加密,前面2块长度为8的报文需要调用update进行部分加密,部分加密的结果可以从update的返回值获取到,最后的7长度(其实一般会填充到长度为块长度8)的报文则调用doFinal进行加密,结束整个部分加密的操作。

另外,值得注意的是只要Cipher正常调用完任一个doFinal变体方法(过程中不抛出异常),那么Cipher会重置为初始化状态,可以继续使用,这个可复用的特性可以降低创建Cipher实例的性能损耗。

各模式示例

加密模式

public String encryptByAes(String content, String password) throws Exception {
	//这里指定了算法为AES_128,工作模式为EBC,填充模式为NoPadding
	Cipher cipher = Cipher.getInstance("AES_128/ECB/NoPadding");
	KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
	//因为AES要求密钥的长度为128,我们需要固定的密码,因此随机源的种子需要设置为我们的密码数组
	keyGenerator.init(128, new SecureRandom(password.getBytes()));
	SecretKey secretKey = keyGenerator.generateKey();
	SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
	//基于加密模式和密钥初始化Cipher
	cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
	//单部分加密结束,重置Cipher
	byte[] bytes = cipher.doFinal(content.getBytes());
	//加密后的密文由二进制序列转化为十六进制序列,依赖apache-codec包
	return Hex.encodeHexString(bytes);
}

其实整个过程Cipher的使用都很简单,比较复杂的反而是密钥生成的过程。上面的例子需要注意,因为使用了填充模式为NoPadding,输入的需要加密的报文长度必须是16(128bit)的倍数。

解密模式


public String decryptByAes(String content, String password) throws Exception {
	//这里要把十六进制的序列转化回二进制的序列,依赖apache-codec包
	byte[] bytes = Hex.decodeHex(content);
	//这里指定了算法为AES_128,工作模式为EBC,填充模式为NoPadding
	Cipher cipher = Cipher.getInstance("AES_128/ECB/NoPadding");
	KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
	//因为AES要求密钥的长度为128,我们需要固定的密码,因此随机源的种子需要设置为我们的密码数组
	keyGenerator.init(128, new SecureRandom(password.getBytes()));
	SecretKey secretKey = keyGenerator.generateKey();
	SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
	//基于解密模式和密钥初始化Cipher
	cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
	//单部分加密结束,重置Cipher
	byte[] result = cipher.doFinal(bytes);
	return new String(result);
}

上面的例子需要注意,因为使用了填充模式为NoPadding,输入的需要加密的报文长度必须是16(128bit)的倍数。

包装密钥模式和解包装密钥模式

密钥的包装和解包装模式是一对互逆的操作,主要作用是通过算法对密钥进行加解密,从而提高密钥泄漏的难度。

package com.wp.crypto;

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class TestEncrypt {

    private static final String SECRECT = "passwrod";

    public String wrap(String keyString) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //初始化密钥生成器,指定密钥长度为128,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成加密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");
        // 通过secretKeySpec,来指定填充模式
        cipher.init(Cipher.WRAP_MODE, secretKeySpec);

        SecretKeySpec key = new SecretKeySpec(keyString.getBytes(), "AES");
        byte[] bytes = cipher.wrap(key);
        // 将字节数组,转为16进制字符串
        return Hex.encodeHexString(bytes);
    }

    public String unwrap(String keyString) throws Exception {
        byte[] rawKey = Hex.decodeHex(keyString);
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //初始化密钥生成器,指定密钥长度为128,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(128, new SecureRandom(SECRECT.getBytes()));
        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成解密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES");

        cipher.init(Cipher.UNWRAP_MODE, secretKeySpec);

        // 解密
        SecretKey key = (SecretKey) cipher.unwrap(rawKey, "AES", Cipher.SECRET_KEY);
        return new String(key.getEncoded());
    }

    public static void main(String[] args) throws Exception {
        TestEncrypt testEncrypt = new TestEncrypt();
        String wrapKey = testEncrypt.wrap("doge");
        System.out.println(wrapKey);
        System.out.println(testEncrypt.unwrap(wrapKey));
    }
}

分组(部分)加密和分组解密

当一个需要加密的报文十分长的时候,我们可以考虑把报文切割成多个小段,然后针对每个小段进行加密,这就是分组加密。分组解密的过程类同,可以看作是分组加密的逆向过程。下面还是用AES算法为例举个例子:

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

/**
 * @author throwable
 * @version v1.0
 * @description
 * @since 2018/8/15 1:06
 */
public enum Part {

	/**
	 * SINGLETON
	 */
	SINGLETON;

	private static final String PASSWORD = "throwable";

	private Cipher createCipher() throws Exception {
		return Cipher.getInstance("AES");
	}

	public String encrypt(String content) throws Exception {
		Cipher cipher = createCipher();
		KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
		//因为AES要求密钥的长度为128,我们需要固定的密码,因此随机源的种子需要设置为我们的密码数组
		keyGenerator.init(128, new SecureRandom(PASSWORD.getBytes()));
		SecretKey secretKey = keyGenerator.generateKey();
		SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
		//基于加密模式和密钥初始化Cipher
		cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
		byte[] raw = content.getBytes();
		StringBuilder builder = new StringBuilder();
		//[0,9]
		byte[] first = cipher.update(raw, 0, 10);
		builder.append(Hex.encodeHexString(first));
		//[10,19]
		byte[] second = cipher.update(raw, 10, 10);
		builder.append(Hex.encodeHexString(second));
		//[20,25]
		byte[] third = cipher.update(raw, 20, 6);
		builder.append(Hex.encodeHexString(third));
		//多部分加密结束,得到最后一段加密的结果,重置Cipher
		byte[] bytes = cipher.doFinal();
		String last = Hex.encodeHexString(bytes);
		builder.append(last);
		return builder.toString();
	}

	public String decrypt(String content) throws Exception {
		byte[] raw = Hex.decodeHex(content);
		Cipher cipher = createCipher();
		KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
		//因为AES要求密钥的长度为128,我们需要固定的密码,因此随机源的种子需要设置为我们的密码数组
		keyGenerator.init(128, new SecureRandom(PASSWORD.getBytes()));
		SecretKey secretKey = keyGenerator.generateKey();
		SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
		//基于解密模式和密钥初始化Cipher
		cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
		StringBuilder builder = new StringBuilder();
		//[0,14]
		byte[] first = cipher.update(raw, 0, 15);
		builder.append(new String(first));
		//[15,29]
		byte[] second = cipher.update(raw, 15, 15);
		builder.append(new String(second));
		//[30,31]
		byte[] third = cipher.update(raw, 30, 2);
		builder.append(new String(third));
		//多部分解密结束,得到最后一段解密的结果,重置Cipher
		byte[] bytes = cipher.doFinal();
		builder.append(new String(bytes));
		return builder.toString();
	}

	public static void main(String[] args) throws Exception{
		String raw = "abcdefghijklmnopqrstyuwxyz";
		String e = Part.SINGLETON.encrypt(raw);
		System.out.println(e);
		System.out.println(Part.SINGLETON.decrypt(e));
	}
}

查看当前JDK中Cipher的所有提供商

我们可以直接查看当前的使用的JDK中Cipher的所有提供商和支持的加解密服务,简单写个main函数就行:

import java.security.Provider;
import java.security.Security;
import java.util.Set;

public class Main {

	public static void main(String[] args) throws Exception {
		Provider[] providers = Security.getProviders();
		if (null != providers) {
			for (Provider provider : providers) {
				Set<Provider.Service> services = provider.getServices();
				for (Provider.Service service : services) {
					if ("Cipher".equals(service.getType())) {
						System.out.println(String.format("provider:%s,type:%s,algorithm:%s", service.getProvider(), service.getType(), service.getAlgorithm()));
					}
				}
			}
		}
	}
}

输出:

provider:SunJCE version 1.8,type:Cipher,algorithm:RSA
provider:SunJCE version 1.8,type:Cipher,algorithm:DES
provider:SunJCE version 1.8,type:Cipher,algorithm:DESede
provider:SunJCE version 1.8,type:Cipher,algorithm:DESedeWrap
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithMD5AndDES
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithMD5AndTripleDES
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndDESede
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC2_40
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC2_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC4_40
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithSHA1AndRC4_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA1AndAES_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA224AndAES_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA256AndAES_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA384AndAES_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA512AndAES_128
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA1AndAES_256
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA224AndAES_256
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA256AndAES_256
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA384AndAES_256
provider:SunJCE version 1.8,type:Cipher,algorithm:PBEWithHmacSHA512AndAES_256
provider:SunJCE version 1.8,type:Cipher,algorithm:Blowfish
provider:SunJCE version 1.8,type:Cipher,algorithm:AES
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_128/ECB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_128/CBC/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_128/OFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_128/CFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_128/GCM/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_192/ECB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_192/CBC/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_192/OFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_192/CFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_192/GCM/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_256/ECB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_256/CBC/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_256/OFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_256/CFB/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AES_256/GCM/NoPadding
provider:SunJCE version 1.8,type:Cipher,algorithm:AESWrap
provider:SunJCE version 1.8,type:Cipher,algorithm:AESWrap_128
provider:SunJCE version 1.8,type:Cipher,algorithm:AESWrap_192
provider:SunJCE version 1.8,type:Cipher,algorithm:AESWrap_256
provider:SunJCE version 1.8,type:Cipher,algorithm:RC2
provider:SunJCE version 1.8,type:Cipher,algorithm:ARCFOUR
provider:SunMSCAPI version 1.8,type:Cipher,algorithm:RSA
provider:SunMSCAPI version 1.8,type:Cipher,algorithm:RSA/ECB/PKCS1Padding

工作中使用

DES加密

package com.wp.crypto;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class TestDes {

    public static void main(String[] args) throws DecoderException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
        String content = "http://192.168.10.120:8088/box/getDetails?boxno=123456789&param=5689";
        String password = "admin123";
        TestDes testDes = new TestDes();
        //
        String encryptedContent = testDes.encrypt(content, password);
        System.out.println(encryptedContent);

        //
        String decryptedContent = testDes.decrypt(encryptedContent, password);
        System.out.println(decryptedContent);
    }

    private String encrypt(String content,String password) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, DecoderException, IllegalBlockSizeException, BadPaddingException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");

        //初始化密钥生成器,指定密钥长度为56,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(56, new SecureRandom(password.getBytes(StandardCharsets.UTF_8)));

        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成加密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "DES");

        //
        Cipher cipher = Cipher.getInstance("DES");

        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        byte[] bytes = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        return Hex.encodeHexString(bytes);
    }

    private String decrypt(String encryptedContent,String password) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, DecoderException, IllegalBlockSizeException, BadPaddingException {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");

        //初始化密钥生成器,指定密钥长度为56,指定随机源的种子为指定的密钥(这里是"passward")
        keyGenerator.init(56, new SecureRandom(password.getBytes(StandardCharsets.UTF_8)));

        SecretKey secretKey = keyGenerator.generateKey();
        // 对明文密钥,标准化成加密算法指定的格式和长度
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "DES");

        //
        Cipher cipher = Cipher.getInstance("DES");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

        byte[] bytes = cipher.doFinal(Hex.decodeHex(encryptedContent));
        return new String(bytes,StandardCharsets.UTF_8);
    }
}

输出

f12328a174c2066199eed1a2a5fa8f8fb326064abf3843ee5998a0c22e765aa8c9fd2e75a8551df612263e29fcf8fe3b9e02135cf33d51fb0b00c8c1699bceacca222028a7cb3d5a
http://192.168.10.120:8088/box/getDetails?boxno=123456789&param=5689

评论