java 加密算法的简单使用
简介
加密算法,就是将原本的明文,通过一系列操作变成密文。在这里介绍一些常用的加密算法。在日常开发中,接触到了一些加密算法,例如,用户的隐私信息,诸如密码、手机号等,需要加密后存储到数据库中,返回给用户时又需要进行解密,在这里对常见的加密算法做一个介绍。
常用加密算法
加密算法的分类:可以简单地分为信息摘要算法、对称加密算法、非对称加密算法。
- 信息摘要算法:根据数据计算出一个固定长度的摘要,这个摘要无法被转换为原本的信息,这种算法可以用来确保信息没有被篡改。
- 对称加密算法:使用一个秘钥对数据进行加密,同时使用相同的秘钥对数据解密。
- 非对称加密算法:用户持有一对公钥和私钥,然后把公钥钥分发给别人。
- 数据加密和解密:公钥加密数据,私钥解密数据。
- 通信:如果两个用户互相交换公钥,它们就可以通信了。
- 签名:私钥签名,公钥验签,确保数据来源是可信的。
信息摘要算法
MD5算法
MD5算法:信息摘要算法5,Message-Digest Alogrithm 5,是一种广泛应用的密码散列算法,于1991年被提出。MD5算法可将消息压缩成固定长度的摘要,通常是16字节(128位),并且这个摘要无法被转换为明文。
MD5算法存在一定程度的安全隐患,因为生成的加密字符串的长度相对较短,容易受到暴力攻击
案例:
public class CipherUtil {// 算法名的枚举public enum MsgDigestName {MD5, SHA1, SHA256}// 信息摘要算法public static byte[] messageDigest(MsgDigestName name, String str) {if (name == null) {return null;}try {MessageDigest sha1 = MessageDigest.getInstance(name.name());sha1.update(str.getBytes(StandardCharsets.UTF_8));return sha1.digest();} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}
}
使用场景:使用MD5算法,加密用户输入的密码,然后把密码保存到数据库中,用户输入密码时,先生成MD5串,然后再去数据库中比对,这样一来,数据库中不会存储用户密码,除了用户自己,谁也不知道他的密码
SHA1算法
SHA1算法:安全哈希算法,Secure Hash Algorithm 1,和MD5一样,也是信息摘要算法。SHA1是美国国家安全局(NSA)制定的安全哈希算法,于1995年发布。SHA1根据输入数据,生成一个20字节(168位)的哈希值。SHA1算法相对于MD5更加安全,但仍然存在哈希碰撞的问题。
SHA256算法
SHA256:SHA256 是 SHA-2 家族中的一种算法,由美国国家安全局制定,于2001年发布。SHA256根据输入数据生成一个32字节(256位)的哈希值。SHA256 算法相对于MD5和SHA1更加安全,被广泛应用于比特币等场景。
SHA1算法、SHA256算法,和MD5算法,都是使用相同的API,只是入参不同。
涉及到的API
java.security.MessageDigest类
提供了消息摘要算法的功能,包括MD5、SHA-1、SHA-256算法
使用案例:
public static byte[] sha256Encode(String str) throws NoSuchAlgorithmException {// 第一步:获取MessageDigest实例,指定要使用的算法的名称MessageDigest sha1Instance = MessageDigest.getInstance("SHA-256");// 第二步:使用update方法,向实例中设置数据sha1Instance.update(str.getBytes(StandardCharsets.UTF_8));// 第三步:调用digest方法,生成加密摘要,return sha1Instance.digest();
}
对称加密算法
DES算法
DES:Data Encryption Standard,数据加密标准,是一种对称加密算法,但是在安全性、效率和灵活性上比AES略差
案例:
public class DESUtil {private static final String DES = "DES";private static final String PADDING_WAY = "DES/ECB/PKCS5Padding";// 加密public static byte[] encode(String str, String pwd) {try {// 生成密钥SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(DES);DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes(StandardCharsets.UTF_8));SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);// 加密Cipher cipher = Cipher.getInstance(PADDING_WAY);cipher.init(Cipher.ENCRYPT_MODE, secretKey);return cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException |IllegalBlockSizeException | BadPaddingException e) {throw new RuntimeException(e);}}// 解密public static String decode(byte[] bytes, String pwd) {try {// 生成密钥SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(DES);DESKeySpec desKeySpec = new DESKeySpec(pwd.getBytes(StandardCharsets.UTF_8));SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec);// 解密Cipher cipher = Cipher.getInstance(PADDING_WAY);cipher.init(Cipher.DECRYPT_MODE, secretKey);return new String(cipher.doFinal(bytes));} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException | NoSuchPaddingException |IllegalBlockSizeException | BadPaddingException e) {throw new RuntimeException(e);}}
}
在这里案例中,只要传入相同的密码,就会生成相同的密钥,可以把它们缓存起来,增加效率。
AES算法
AES算法:Advanced Encryption Standard,高级加密标准,一种广泛使用的对称加密算法。对称加密意味着加密和解密使用相同的密钥。
AES提供了多种密钥长度,包括 128 位、192 位和 256 位。
案例:
public class AESUtil {private static final String AES = "AES";private static final String SHA1PRNG = "SHA1PRNG";private static final String PADDING_MODE = "AES/ECB/PKCS5Padding";// 加密public static byte[] encode(String str, String pwd) {try {// 生成随机数SecureRandom secureRandom = SecureRandom.getInstance(SHA1PRNG);secureRandom.setSeed(pwd.getBytes(StandardCharsets.UTF_8));// 初始化秘钥生成器,第一个参数指定了秘钥长度,第二个参数指定了随机数源,用于提供加密安全的随机数。// 这确保了生成的密钥是随机的,不容易被预测或重复。KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);keyGenerator.init(256, secureRandom);// 密钥SecretKey secretKey = keyGenerator.generateKey();// 设置加密算法/模式/填充方案Cipher cipher = Cipher.getInstance(PADDING_MODE);cipher.init(Cipher.ENCRYPT_MODE, secretKey);return cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException| IllegalBlockSizeException | BadPaddingException e) {throw new RuntimeException(e);}}// 解密public static String decode(byte[] bytes, String pwd) {try {// 生成随机数SecureRandom secureRandom = SecureRandom.getInstance(SHA1PRNG);secureRandom.setSeed(pwd.getBytes(StandardCharsets.UTF_8));// 生成密钥KeyGenerator aesKeyGenerator = KeyGenerator.getInstance(AES);aesKeyGenerator.init(256, secureRandom);SecretKey secretKey = aesKeyGenerator.generateKey();// 解密数据Cipher cipher = Cipher.getInstance(PADDING_MODE);cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] bytes1 = cipher.doFinal(bytes);return new String(bytes1);} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException| IllegalBlockSizeException | BadPaddingException e) {throw new RuntimeException(e);}}
}
在这个工具类中,只要传入的密码相同,就会生成相同的密钥
相关API:
1、创建随机数生成器时指定的参数:SHA1PRNG,它是一种基于SHA-1算法的伪随机数生成器算法,其原理是通过SHA-1算法生成不可预测的伪随机数序列,以达到保障密码安全的目的。PRNG:pseudo random number generator,伪随机数生成器
2、创建Cipher实例时指定的参数:AES/ECB/PKCS5Padding,这个参数分为3个部分:
- AES是加密算法
- ECB:Electronic Codebook,电子密码本,ECB 是AES 的一个操作模式。在 ECB 模式下,每个块(通常是 128 位或 16 字节)独立地加密。这意味着,如果原始数据中存在重复的块,那么加密后的数据中也会存在相同的块。这种模式简单、易于实现,但在某些情况下可能不太安全,因为它不提供任何形式的混淆。
- PKCS5Padding:Public Key Cryptography Standards,公钥加密标准,指定了填充方式。填充方式用于确保待加密的数据长度符合特定算法的要求。对于某些算法,数据必须达到特定长度才能被加密。PKCS5Padding 是 PKCS#5 标准定义的填充方式,它使用一个字节的填充值来达到这个长度。填充值是原始数据长度与块大小之间的差值。
涉及到的API
jce.jar:java cipher extension,java加密扩展,提供了加密的功能
javax.crypto
KeyGenerator类
用于生成秘钥,它可以生成对称秘钥,比如AES、DES、RSA等算法的秘钥。
使用案例:
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;// 学习KeyGenerator中的方法
public class Test1 {public static void main(String[] args) throws NoSuchAlgorithmException {// 获取KeyGenerator实例,它的底层是基于spi机制KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");// 初始化KeyGenerator,设置秘钥长度为128位keyGenerator.init(128);// 生成密钥,随后可以使用这个秘钥来进行加密和解密SecretKey secretKey = keyGenerator.generateKey();// 查看keyGenerator中的属性String algorithm = keyGenerator.getAlgorithm();System.out.println("algorithm = " + algorithm); // AESProvider provider = keyGenerator.getProvider();System.out.println("provider = " + provider); // SunJCE version 1.8}
}
SecretKey接口
代表一个秘钥,用于对称加密算法,它提供了管理密钥的方法,如生成、复制、销毁等。
代码:
public interface SecretKey extends Key, Destroyable {long serialVersionUID = -4795878709595146952L;
}
SecretKey接口的父接口:
public interface Key extends java.io.Serializable {static final long serialVersionUID = 6603384152749567654L;public String getAlgorithm(); // 密钥使用的算法public String getFormat();public byte[] getEncoded(); // 密钥的字节码
}
SecretKeySpec类
用于生成秘钥
案例:
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;// 学习SecretKey
public class Test3 {public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {// 测试数据String data = "Hello World!";// 16字节的秘钥,长度是128,用于AES算法。这个秘钥在这里是明文,但是它通常应该是一个密文byte[] bytes = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);// 生成秘钥SecretKey secretKey = new SecretKeySpec(bytes, "AES");// 加密Cipher cipher = Cipher.getInstance("AES");cipher.init(Cipher.ENCRYPT_MODE, secretKey);byte[] bytes1 = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));// [115, -27, 109, -94, 63, -59, 24, 123, 100, 86, 107, -90, -29, -34, -10, 126]System.out.println("bytes1 = " + Arrays.toString(bytes1));// 解密cipher.init(Cipher.DECRYPT_MODE, secretKey);byte[] bytes2 = cipher.doFinal(bytes1);// Hello World!System.out.println("new String(bytes2) = " + new String(bytes2));}
}
Cipher类
提供了一种通用的API来执行加密操作。
常用方法:
init(int opmode, Key key)
: 初始化 Cipher 对象,指定操作模式(加密或解密)和密钥。doFinal(byte[] input)
: 完成加密或解密操作,并返回结果。getAlgorithm()
:获取用于此 Cipher 实例的算法名称。- update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset): 加密或解密输入数据,并将结果存储在输出缓冲区中。
案例:
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;// Cipher类的使用
public class Test4 {public static void main(String[] args) throws Exception {// 生成秘钥KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");keyGenerator.init(128);SecretKey secretKey = keyGenerator.generateKey();byte[] encoded = secretKey.getEncoded(); // 秘钥// 加密数据String data = "离离原上草";Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 设置加密算法、模式和填充方案cipher.init(Cipher.ENCRYPT_MODE, secretKey); // 初始化加密操作byte[] encodedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); // 加密// [104, -50, 82, 71, -113, -66, 16, -128, 39, -52, -18, 4, -13, -88, -32, -42]System.out.println("encodedBytes = " + Arrays.toString(encodedBytes));// 解密数据Cipher cipher1 = Cipher.getInstance("AES/ECB/PKCS5Padding");cipher1.init(Cipher.DECRYPT_MODE, secretKey);byte[] decodedBytes = cipher1.doFinal(encodedBytes); // 解密// 离离原上草System.out.println("new String(decodedBytes) = " + new String(decodedBytes));}
}
java.security
SecureRandom类
用于生成安全的随机数,生成的随机数是加密安全的,这些随机数对于加密算法和密钥生成来说是必要的,因为它们需要具有高度不可预测性和不可重复性。当前类是线程安全的。
案例:
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;// 学习SecureRandom类
public class Test2 {public static void main(String[] args) throws NoSuchAlgorithmException {SecureRandom secureRandom = new SecureRandom();// 设置随机数生成器的种子值。种子是随机数生成器的起点,用于决定生成随机数序列的起始状态。// 通过设置相同的种子值,可以确保每次运行程序时生成的随机数序列是相同的。secureRandom.setSeed("123456".getBytes(StandardCharsets.UTF_8));// 生成一个随机字节数组byte[] bytes = new byte[16];secureRandom.nextBytes(bytes);// [107, -76, -125, 126, -73, 67, 41, 16, 94, -28, 86, -115, -38, 125, -58, 126]System.out.println("bytes = " + Arrays.toString(bytes));// 生成一个随机数int i = secureRandom.nextInt();// -758502695System.out.println("i = " + i);// 获取随机数生成器时指定生成随机数的算法,SHA1PRNG是基于SHA1的随机数生成算法。// PRNG:Pseudo Random Number Generator,伪随机数生成器是一种算法或设备,// 用于生成一系列看似随机但实际上具有一定规律的数字序列。这些数字序列对于加密和安全// 应用来说是必要的,因为它们需要具有不可预测性和不可重复性。SecureRandom secureRandom1 = SecureRandom.getInstance("SHA1PRNG");secureRandom1.setSeed("123456".getBytes(StandardCharsets.UTF_8));int i1 = secureRandom1.nextInt();// 1806992254System.out.println("i1 = " + i1);}
}
非对称加密算法
RSA算法
RSA算法:一种非对称加密算法,它的安全性依赖于大数分解的难度。RSA算法使用一对密钥,其中一个密钥是公开的(即公钥),另一个密钥是保密的(即私钥)。公钥用于加密数据,而私钥用于解密数据。RSA是发明这个算法的三个人名字的首字母。
RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
RSA算法的安全性依赖于大数分解的难度,而其可靠性则取决于因数分解的难度。因此,对于极长的密钥(如超过几百位十进制),RSA算法被认为是足够安全的。然而,对于较短的密钥长度(如几十位十进制),存在一些已知的攻击方法,可能会破解密钥。因此,在实际应用中,建议使用足够长的密钥长度以保证安全性。
RSA的主要应用就是对数据进行加密、解密、签名、验签。使用私钥进行签名,使用公钥验签,确保数据来源。
案例:
public class RSAUtil {private static final String RSA = "RSA";private static final String MD5_WITH_RSA = "MD5withRSA";// 初始化公钥和私钥private static final PrivateKey privateKey;private static final PublicKey publicKey;static {try {// 生成公钥和私钥KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);keyPairGenerator.initialize(1024);KeyPair keyPair = keyPairGenerator.generateKeyPair();privateKey = keyPair.getPrivate();publicKey = keyPair.getPublic();} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}// 加密public static byte[] encode(String str) {try {Cipher cipher = Cipher.getInstance(RSA);cipher.init(Cipher.ENCRYPT_MODE, publicKey); // 使用公钥进行加密,Cipher设置为加密模式return cipher.doFinal(str.getBytes(StandardCharsets.UTF_8));} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |BadPaddingException e) {throw new RuntimeException(e);}}// 解密public static String decode(byte[] bytes) {try {Cipher cipher = Cipher.getInstance(RSA);cipher.init(Cipher.DECRYPT_MODE, privateKey); // 使用私钥进行解密,Cipher设置为解密模式return new String(cipher.doFinal(bytes));} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |BadPaddingException e) {throw new RuntimeException(e);}}// 签名,使用私钥签名public static byte[] sign(String data) {try {Signature signature = Signature.getInstance(MD5_WITH_RSA);signature.initSign(privateKey);signature.update(data.getBytes(StandardCharsets.UTF_8));return signature.sign();} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {throw new RuntimeException(e);}}// 验证签名,使用公钥验证签名public static boolean verify(String data, byte[] signs) {try {Signature signature = Signature.getInstance(MD5_WITH_RSA);signature.initVerify(publicKey);signature.update(data.getBytes(StandardCharsets.UTF_8));return signature.verify(signs);} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {throw new RuntimeException(e);}}
}
涉及到的API
java.security
KeyPairGenerator类
生成公钥和私钥,它们是一对的,用于支持非对称加密算法,包括RSA、DSA等
案例:
import java.security.*;public class Test6 {public static void main(String[] args) throws NoSuchAlgorithmException {// 1. 创建一个 KeyPairGenerator 实例,指定 RSA 作为算法KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");// 2. 指定密钥长度,例如2048位,这一步是可选的keyGen.initialize(2048);// 3. 生成密钥对KeyPair keyPair = keyGen.generateKeyPair();// 4. 获取私钥和公钥PrivateKey privateKey = keyPair.getPrivate();PublicKey publicKey = keyPair.getPublic();System.out.println("privateKey = " + privateKey);System.out.println("publicKey = " + publicKey);}
}
Signature类
用于对数据进行签名和验证签名。签名是一种确保数据完整性和身份验证的方法。当用户对数据进行签名时,实际上是在数据使用私钥和数据函数,生成一个唯一的字符串,这个字符串依赖于原始数据。之后,任何人都可以使用相同的函数和用户的公钥来验证签名是否有效。
案例:
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;public class Test8 {public static void main(String[] args) throws Exception {String data = "Hello, World!";// 1. 生成密钥对KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");// 指定秘钥的长度keyGen.initialize(2048);KeyPair pair = keyGen.generateKeyPair();PrivateKey privateKey = pair.getPrivate();PublicKey publicKey = pair.getPublic();// 2. 使用私钥进行签名Signature privateSignature = Signature.getInstance("SHA256withRSA");privateSignature.initSign(privateKey);// 设置数据privateSignature.update(data.getBytes(StandardCharsets.UTF_8));// 生成签名数据byte[] signature = privateSignature.sign();// 3. 使用公钥进行验证Signature publicSignature = Signature.getInstance("SHA256withRSA");publicSignature.initVerify(publicKey);// 设置数据publicSignature.update(data.getBytes());// 验签boolean isValid = publicSignature.verify(signature);System.out.println("验签结果 == " + isValid); // true}
}
PKCS8EncodedKeySpec类
用于解析从 PKCS#8 格式编码的私钥,PKCS#8 是 Public-Key Cryptography Standards (公钥密码学标准) 的一个部分,它定义了私钥的格式。
当用户有一个 PKCS#8 格式的私钥时,可以使用 PKCS8EncodedKeySpec
类来解析它,然后使用 KeyFactory
类来创建一个私钥对象。
案例:
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;public class Test7 {public static void main(String[] args) throws Exception {// 1. 创建一个 KeyPairGenerator 实例,指定 RSA 作为算法KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");// 2. 指定密钥长度,例如2048位,这一步是可选的keyGen.initialize(2048);// 3. 生成密钥对KeyPair keyPair = keyGen.generateKeyPair();// 4. 获取私钥和公钥PrivateKey privateKey = keyPair.getPrivate();// 5. 使用PKCS8EncodedKeySpec解析私钥KeyFactory keyFactory = KeyFactory.getInstance("RSA");PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());PrivateKey privateKey1 = keyFactory.generatePrivate(keySpec);// privateKey == privateKey1System.out.println("privateKey == privateKey1 = " + (privateKey.equals(privateKey1))); // true// sun.security.rsa.RSAPrivateCrtKeyImpl@3f70fSystem.out.println("privateKey = " + privateKey);// sun.security.rsa.RSAPrivateCrtKeyImpl@3f70fSystem.out.println("privateKey1 = " + privateKey1);// 私钥的内容是相同的System.out.println("privateKey.getEncoded() = " + Arrays.toString(privateKey.getEncoded()));System.out.println("privateKey1.getEncoded() = " + Arrays.toString(privateKey1.getEncoded()));}
}
X509EncodedKeySpec
用于从 X.509 格式的秘钥编码中解析密钥。
案例:
import java.security.*;
import java.security.spec.X509EncodedKeySpec;public class Test9 {public static void main(String[] args) throws Exception {// 1. 创建一个 KeyPairGenerator 实例,指定 RSA 作为算法KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");// 2. 指定密钥长度,例如2048位,这一步是可选的keyGen.initialize(2048);// 3. 生成密钥对KeyPair keyPair = keyGen.generateKeyPair();// 4. 获取私钥和公钥PublicKey publicKey = keyPair.getPublic();// 5. 根据公钥中的字节,生成公钥对象KeyFactory keyFactory = KeyFactory.getInstance("RSA");X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey.getEncoded());PublicKey publicKey1 = keyFactory.generatePublic(keySpec);System.out.println("publicKey1.equals(publicKey) = " + publicKey1.equals(publicKey));}
}
其它:字节数组和字符串之间的转换
使用加密算法加密出的数据是字节数组,为了方便查看字节数组,可以将字节数组转换为字符串,常用的转换方式有两种,Base64编码和将字节数组转换为十六进制字符串
Base64编码
一种基于64个可打印字符来表示二进制数据的表示方法,它用于将二进制数据转换为可打印字符的形式。因为传统的文本编码方式,如ASCII编码,无法直接表示二进制数据,因此在某些情况下,需要一种编码方式,能够将二进制数据转换为文本形式,便于传输和存储。
Base64 中的可打印字符包括字母 A-Z、a-z、0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
Base64 编码的基本步骤:
- 将数据划分为3个字节一组,共24位。
- 将24位数据按照6位一组进行划分,得到4个6位的组,将每个6位的组转换为对应的Base64字符,如果数据不足3字节,进行填充
- 将所有转换后的 Base64字符连接起来,形成最终的编码结果,编码后的数据长度总是比原始数据长约 1/3
Base64 编码是一种可逆的编码方式,可以通过解码还原原始数据。
案例:
public class Base64Test {@Testpublic void test1() {byte[] bytes = "1234567890".getBytes(StandardCharsets.UTF_8);// 将字节数组编码为字符串String s = Base64.getEncoder().encodeToString(bytes);// MTIzNDU2Nzg5MA==System.out.println("s = " + s);// 将编码后的结果解码为字节数组byte[] decode = Base64.getDecoder().decode(s);// 1234567890System.out.println("new String(decode) = " + new String(decode));}
}
字节数组和十六进制字符串之间的相互转换
另一种常见的处理字节数组的方式,是把它转换为表示十六进制的字符串,一个字节,每4比特转换为一个十六进制的字符。
案例:
// 字节数组转换为十六进制字符串
public static String toHexString(byte[] bytes) {
final char[] HEX_CHAR_ARR = “0123456789ABCDEF”.toCharArray();
// 将一个字节转换为两个十六进制字符,使用查表法,性能最高StringBuilder sBuilder = new StringBuilder();for (byte b : bytes) {// b无符号右位移时会先被扩展为int,所以结果和0x0F按位与,只取最后四位的值sBuilder.append(HEX_CHAR_ARR[(b >>> 4) & 0x0F]) // 第一个字符.append(HEX_CHAR_ARR[b & 0x0F]); // 第二个字符}return sBuilder.toString();
}// 十六进制字符串再转换为字节数组
public static byte[] toBytes(String str) {char[] charArray = str.toCharArray();byte[] bytes = new byte[charArray.length / 2];for (int i = 0; i < charArray.length; i += 2) { // 注意,这里一次前进两位char c = charArray[i];char c1 = charArray[i + 1];// 每两个十六进制数字转换为一个字节// 案例:95A2AA27D3FF301FFF665E98106D2997,在这个字符串中,两个字符代表一个// 字节的数字,在这里截取出第一个字符和第二个字符,按照十六进制数把它们解析为int类,// 然后第一个数字向左位移4位,和第二个数字加在一起,就是一个字节的数据。以此类推。int i1 = (Character.digit(c, 16) << 4) + (Character.digit(c1, 16));bytes[i / 2] = (byte) i1;}return bytes;
}
总结
这里简要介绍了在选择加密解密算法时涉及到的API,如果是程序内部使用的加密解密算法,推荐使用对称加密算法,如果需要和外部交互,比如前端传入的用户密码,可以使用非对称加密算法,前端使用公钥加密用户敏感数据,后端使用私钥解密。字节数组不方便查看,可以使用base64算法将它们转换为字符串