Java AES加密如何使用zeropadding方式填充

前一久,在对接支付通道时,遇到上游使用AES加密方式,对方要求加密时使用CBC模式,zeropadding填充,偏移量为0000*4(即16个0),输出十六进制,字符集使用UTF-8。

本以为也没什么问题,可到实际开发时却发现Java虽然支持AES的CBC模式,但填充方式却没有zeropadding模式。通过查看文档,先梳理一下加密算法相关的知识。

JDK1.8支持的加密算法:

Cipher Algorithm Names

The following names can be specified as the algorithm component in a transformation when requesting an instance of Cipher.

Algorithm Name Description
AES

Advanced Encryption Standard as specified by NIST in FIPS 197. Also known as the Rijndael algorithm by Joan Daemen and Vincent Rijmen, AES is a 128-bit block cipher supporting keys of 128, 192, and 256 bits.

To use the AES cipher with only one valid key size, use the format AES_, where can be 128, 192, or 256.

AESWrap

The AES key wrapping algorithm as described in RFC 3394.

To use the AESWrap cipher with only one valid key size, use the format AESWrap_, where can be 128, 192, or 256.

ARCFOUR A stream cipher believed to be fully interoperable with the RC4 cipher developed by Ron Rivest. For more information, see K. Kaukonen and R. Thayer, "A Stream Cipher Encryption Algorithm 'Arcfour'", Internet Draft (expired), draft-kaukonen-cipher-arcfour-03.txt.
Blowfish The Blowfish block cipher designed by Bruce Schneier.
DES The Digital Encryption Standard as described in FIPS PUB 46-3.
DESede Triple DES Encryption (also known as DES-EDE, 3DES, or Triple-DES). Data is encrypted using the DES algorithm three separate times. It is first encrypted using the first subkey, then decrypted with the second subkey, and encrypted with the third subkey.
DESedeWrap The DESede key wrapping algorithm as described in RFC 3217 .
ECIES Elliptic Curve Integrated Encryption Scheme
PBEWithAnd PBEWithAnd The password-based encryption algorithm found in (PKCS5), using the specified message digest () or pseudo-random function () and encryption algorithm (). Examples:
  • PBEWithMD5AndDES: The password-based encryption algorithm as defined in RSA Laboratories, "PKCS #5: Password-Based Encryption Standard," version 1.5, Nov 1993. Note that this algorithm implies CBC as the cipher mode and PKCS5Padding as the padding scheme and cannot be used with any other cipher modes or padding schemes.
  • PBEWithHmacSHA256AndAES_128: The password-based encryption algorithm as defined in RSA Laboratories, "PKCS #5: Password-Based Cryptography Standard," version 2.0, September 2000 .
RC2 Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc.
RC4 Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc. (See note prior for ARCFOUR.)
RC5 Variable-key-size encryption algorithms developed by Ron Rivest for RSA Data Security, Inc.
RSA The RSA encryption algorithm as defined in PKCS #1

 

支持的加密算法模式:

Cipher Algorithm Modes

The following names can be specified as the mode component in a transformation when requesting an instance of Cipher.

Algorithm Name Description
NONE No mode.
CBC Cipher Block Chaining Mode, as defined in FIPS PUB 81.
CCM Counter/CBC Mode, as defined in NIST Special Publication SP 800-38C.
CFB, CFBx Cipher Feedback Mode, as defined in FIPS PUB 81.

Using modes such as CFB and OFB, block ciphers can encrypt data in units smaller than the cipher's actual block size. When requesting such a mode, you may optionally specify the number of bits to be processed at a time by appending this number to the mode name as shown in the "DES/CFB8/NoPadding" and "DES/OFB32/PKCS5Padding" transformations. If no such number is specified, a provider-specific default is used. (For example, the SunJCE provider uses a default of 64 bits for DES.) Thus, block ciphers can be turned into byte-oriented stream ciphers by using an 8-bit mode such as CFB8 or OFB8.
CTR A simplification of OFB, Counter mode updates the input block as a counter.
CTS Cipher Text Stealing, as described in Bruce Schneier's book Applied Cryptography-Second Edition, John Wiley and Sons, 1996.
ECB Electronic Codebook Mode, as defined in FIPS PUB 81 (generally this mode should not be used for multiple blocks of data).
GCM Galois/Counter Mode, as defined in NIST Special Publication SP 800-38D.
OFB, OFBx Output Feedback Mode, as defined in FIPS PUB 81.

Using modes such as CFB and OFB, block ciphers can encrypt data in units smaller than the cipher's actual block size. When requesting such a mode, you may optionally specify the number of bits to be processed at a time by appending this number to the mode name as shown in the "DES/CFB8/NoPadding" and "DES/OFB32/PKCS5Padding" transformations. If no such number is specified, a provider-specific default is used. (For example, the SunJCE provider uses a default of 64 bits for DES.) Thus, block ciphers can be turned into byte-oriented stream ciphers by using an 8-bit mode such as CFB8 or OFB8.
PCBC Propagating Cipher Block Chaining, as defined by Kerberos V4.

这里简单说一下加密算法模式,不要把它与加密算法混淆起来,加密算法就是使用何种方式加密,比如:DES、AES、RSA,而加密算法模式,是用来描述加密算法(此处特指分组密码,不包括流密码,)在加密时对明文分组的模式,它代表了不同的分组方式,如常见的:

ECB模式:电子密码本模式

CBC模式:密码分组连接模式

CFB模式:密文反馈模式

OFB模式:输出反馈模式

CTR模式:计数器模式

感兴趣的童鞋可以自行查找资料研究。

 

支持的加密算法填充方式:

Cipher Algorithm Padding

The following names can be specified as the padding component in a transformation when requesting an instance of Cipher.

Algorithm Name Description
NoPadding No padding.
ISO10126Padding This padding for block ciphers is described in 5.2 Block Encryption Algorithms in the W3C's "XML Encryption Syntax and Processing" document.
OAEPPadding, OAEPWithAndPadding Optimal Asymmetric Encryption Padding scheme defined in PKCS1, where should be replaced by the message digest and by the mask generation function. Examples: OAEPWithMD5AndMGF1Padding and OAEPWithSHA-512AndMGF1Padding.

If OAEPPadding is used, Cipher objects are initialized with a javax.crypto.spec.OAEPParameterSpec object to supply values needed for OAEPPadding.
PKCS1Padding The padding scheme described in PKCS #1, used with the RSA algorithm.
PKCS5Padding The padding scheme described in RSA Laboratories, "PKCS #5: Password-Based Encryption Standard," version 1.5, November 1993.
SSL3Padding The padding scheme defined in the SSL Protocol Version 3.0, November 18, 1996, section 5.2.3.2 (CBC block cipher):
    block-ciphered struct {
        opaque content[SSLCompressed.length];
        opaque MAC[CipherSpec.hash_size];
        uint8 padding[
            GenericBlockCipher.padding_length];
        uint8 padding_length;
    } GenericBlockCipher;
The size of an instance of a GenericBlockCipher must be a multiple of the block cipher's block length.

The padding length, which is always present, contributes to the padding, which implies that if:
    sizeof(content) + sizeof(MAC) % block_length = 0, 
padding has to be (block_length - 1) bytes long, because of the existence of padding_length.

This makes the padding scheme similar (but not quite) to PKCS5Padding, where the padding length is encoded in the padding (and ranges from 1 to block_length). With the SSL scheme, the sizeof(padding) is encoded in the always present padding_length and therefore ranges from 0 to block_length-1.

说一下什么是填充方式,填充方式是在分组密码中,当明文长度不是分组长度的整数倍时,需要在最后一个分组中填充一些数据使其凑满一个分组的长度。

看到这,发现根本没有对方要求的zeropadding填充方式,那咋整?还有偏移量是什么鬼?此处。。。。烦躁

最后,查找到一个类似的案例解决了此问题,事后也查了下相关资料,现在先来回答上面两个问题,之后贴出代码。

一、zeropadding填充方式,它是使用“0”作为填充数据的填充方式,也就是说在分组时,最后一组明文的长度没有达到分组长度,那么就用“0”来补足。.Net 平台提供了这种方式,但Java没有提供,但Java提供NoPadding这中方式,意思是不做填充,这肯定不行,那怎么办?其实要的就是你不填充,这样我才能自己填充,我想填什么就填什么,如何填充呢?

二、偏移量,也叫初始化向量(IV),这个概念涉及到CBC模式的原理,那就顺便说一下,方便大家理解,CBC模式在对明文分组加密时,会将明文分组与前一个密文分组进行XOR运算(即异或运算),但是加密第一个明文分组时不存在“前一个密文分组”,因此需要事先准备一个与分组长度相等的比特序列来代替,这个比特序列就是偏移量。

好了,明白这了这两个概念,那么下面就是根据zeropadding填充规则和对方要求的偏移量来自己用代码实现了,直接上代码

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESEncrypt {

	public static String encrypt(String data, String key) {

		String ivString = "0000000000000000";
        //偏移量
		byte[] iv = ivString.getBytes();
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			int blockSize = cipher.getBlockSize();
			byte[] dataBytes = data.getBytes();
			int length = dataBytes.length;
            //计算需填充长度
			if (length % blockSize != 0) {
				length = length + (blockSize - (length % blockSize));
			}
			byte[] plaintext = new byte[length];
            //填充
			System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
			SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
            //设置偏移量参数
			IvParameterSpec ivSpec = new IvParameterSpec(iv);
			cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
			byte[] encryped = cipher.doFinal(plaintext);

			return parseByte2HexStr(encryped);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	public static String desEncrypt(String data, String key) {

		String ivString = "0000000000000000";
		byte[] iv = ivString.getBytes();

		try {
			byte[] encryp = parseHexStr2Byte(data);
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
			IvParameterSpec ivSpec = new IvParameterSpec(iv);
			cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
			byte[] original = cipher.doFinal(encryp);
			return new String(original);
		} catch (Exception e) {
			// TODO: handle exception
		}
		return null;
	}

	public static String parseByte2HexStr(byte[] buf) {
		StringBuffer sb = new StringBuffer();

		for (int i = 0; i < buf.length; ++i) {
			String hex = Integer.toHexString(buf[i] & 255);
			if (hex.length() == 1) {
				hex = '0' + hex;
			}
			sb.append(hex.toUpperCase());
		}

		return sb.toString();
	}

	public static byte[] parseHexStr2Byte(String hexStr) {
		if (hexStr.length() < 1) {
			return null;
		} else {
			byte[] result = new byte[hexStr.length() / 2];

			for (int i = 0; i < hexStr.length() / 2; ++i) {
				int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
				int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
				result[i] = (byte) (high * 16 + low);
			}

			return result;
		}
	}

	public static void main(String[] args) {
		String data = "123456";
		String key = "186751244B391A6DCA84778E0D6A8910";
		String encrypt = encrypt(data, key);
		System.out.println("加密前:" + data);
		System.out.println("加密后:" + encrypt);
		String desEncrypt = desEncrypt(encrypt, key);
		System.out.println("解密后:" + desEncrypt);
	}

}

到这里也算完了,其实对加密这一块大部分人都不是特别熟悉,基本处在会用得阶段,我也一样,但这一块其实还挺有意思的,以后有空要研究一下。

你可能感兴趣的:(Java AES加密如何使用zeropadding方式填充)