除了保护网页和管理访问权限外, Apache Shiro还执行基本的加密任务。 该框架能够:

  • 加密和解密数据,
  • 哈希数据,
  • 生成随机数。

Shiro没有实现任何加密算法。 所有计算都委托给Java密码学扩展(JCE)API。 使用Shiro代替Java中已经存在的主要好处是易于使用和安全的默认值。 Shiro加密模块以更高的抽象级别编写,默认情况下实现所有已知的最佳实践。

这是致力于Apache Shiro的系列文章的第三部分。 第一部分介绍了如何保护Web应用程序并添加登录/注销功能。 第二部分介绍了如何在数据库中存储用户帐户,以及如何为用户提供通过PGP证书进行身份验证的选项。

这篇文章从Shiro和JCE的简短概述开始,并继续介绍一些有用的转换实用程序 。 以下各章介绍了随机数的生成 , 散列以及如何加密和解密数据。 最后一章介绍了如何自定义密码以及如何创建新密码。

总览

Shiro加密模块位于org.apache.shiro.crypto包中。 它没有手册,但是幸运的是,所有加密类都是Javadoc繁重的。 Javadoc包含将用手动编写的所有内容。

Shiro严重依赖于Java密码学扩展。 您无需了解JCE即可使用Shiro。 但是,您需要JCE基础知识来对其进行自定义或添加新功能。 如果您对JCE不感兴趣,请跳到下一章。

JCE是一组高度可定制的API及其默认实现。 它是基于提供程序的。 如果默认实现没有所需的内容,则可以轻松安装新的提供程序。

每个密码,密码选项,哈希算法或任何其他JCE功能都有一个名称。 JCE为算法和算法模式定义了两组 标准名称 。 这些可用于任何JDK。 任何提供程序(例如Bouncy Castle )都可以自由地使用新算法和选项扩展名称集。

名称由所谓的转换字符串组成,用于查找所需的对象。 例如, Cipher.getInstance('DES/ECB/PKCS5Padding')在ECB模式下以PKCS#5填充返回DES密码。 返回的密码通常需要进一步的初始化,不能使用安全的默认值,也不是线程安全的。

Apache Shiro组成转换字符串,配置获取的对象并为其添加线程安全性。 最重要的是,它具有易于使用的API,并添加了无论如何都应实施的更高级别的最佳实践。

编码,解码和ByteSource

加密包对字节数组( byte[] )进行加密,解密和散列。 如果需要加密或哈希字符串,则必须先将其转换为字节数组。 相反,如果需要将散列或加密的值存储在文本文件或字符串数​​据库列中,则必须将其转换为字符串。

文本到字节数组

静态类CodecSupport能够将文本转换为字节数组并返回。 方法byte[] toBytes(String source)将字符串转换为字节数组,而方法String toString(byte[] bytes)将其转换回来。

使用编解码器支持在文本和字节数组之间进行转换 :

@Testpublic void textToByteArray() {String encodeMe = 'Hello, I'm a text.';byte[] bytes = CodecSupport.toBytes(encodeMe);String decoded = CodecSupport.toString(bytes);assertEquals(encodeMe, decoded);}

编码和解码字节数组

从字节数组到字符串的转换称为编码。 反向过程称为解码。 Shiro提供了两种不同的算法:

  • Base64类中实现的Base64
  • Hex类中实现的Hex

这两个类都是静态的,并且都具有encodeToStringdecode实用程序方法。

例子

将随机数组编码为其十六进制表示形式,对其进行解码并验证结果:

@Testpublic void testStaticHexadecimal() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};String hexadecimal = Hex.encodeToString(encodeMe);assertEquals('020406080a0c0e101214', hexadecimal);byte[] decoded = Hex.decode(hexadecimal);assertArrayEquals(encodeMe, decoded);}

将随机数组编码为其Byte64表示形式 ,对其进行解码并验证结果:

@Testpublic void testStaticBase64() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};String base64 = Base64.encodeToString(encodeMe);assertEquals('AgQGCAoMDhASFA==', base64);byte[] decoded = Base64.decode(base64);assertArrayEquals(encodeMe, decoded);}

字节源

加密程序包通常返回ByteSource接口的实例,而不是字节数组。 它的实现SimpleByteSource是字节数组的简单包装,提供了其他可用的编码方法:

  • String toHex() –返回十六进制字节数组表示形式,
  • String toBase64() –返回Base64字节数组表示形式,
  • byte[] getBytes() –返回包装的字节数组。

例子

该测试使用ByteSource将数组编码成其十六进制表示形式。 然后解码并验证结果:

@Testpublic void testByteSourceHexadecimal() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};ByteSource byteSource = ByteSource.Util.bytes(encodeMe);String hexadecimal = byteSource.toHex();assertEquals('020406080a0c0e101214', hexadecimal);byte[] decoded = Hex.decode(hexadecimal);assertArrayEquals(encodeMe, decoded);}

使用Bytesource将数组编码成其Base64表示形式。 对其进行解码并验证结果:

@Testpublic void testByteSourceBase64() {byte[] encodeMe = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};ByteSource byteSource = ByteSource.Util.bytes(encodeMe);String base64 = byteSource.toBase64();assertEquals('AgQGCAoMDhASFA==', base64);byte[] decoded = Base64.decode(base64);assertArrayEquals(encodeMe, decoded);}

随机数发生器

随机数生成器由RandomNumberGenerator接口及其默认实现SecureRandomNumberGenerator组成 。

该接口非常简单,只有两种方法:

  • ByteSource nextBytes() –生成一个随机的固定长度的字节源,
  • ByteSource nextBytes(int numBytes) –生成具有指定长度的随机字节源。

默认实现实现了这两种方法,并提供了一些其他配置:

  • setSeed(byte[] bytes) –自定义种子配置,
  • setDefaultNextBytesSize(int defaultNextBytesSize)nextBytes()输出的长度。

种子是一个数字(实际上是字节数组),用于初始化随机数生成器。 它允许您生成“可预测的随机数”。 使用相同种子初始化的同一随机生成器的两个实例始终生成相同的随机数序列。 它对于调试很有用,但要非常小心。

如果可以,请不要为加密指定自定义种子。 使用默认值。 除非您真的知道自己在做什么,否则攻击者可能会猜出定制的那个。 这将超过随机数的所有安全性目的。

在幕后:SecureRandomNumberGenerator将随机数生成委托给JCE SecureRandom实现。

例子

第一个示例创建两个随机数生成器,并验证它们是否生成两个不同的事物:

@Testpublic void testRandomWithoutSeed() {//create random generatorsRandomNumberGenerator firstGenerator = new SecureRandomNumberGenerator();RandomNumberGenerator secondGenerator = new SecureRandomNumberGenerator();//generate random bytesByteSource firstRandomBytes = firstGenerator.nextBytes();ByteSource secondRandomBytes = secondGenerator.nextBytes();//compare random bytesassertByteSourcesNotSame(firstRandomBytes, secondRandomBytes);}

第二个示例创建两个随机数生成器,使用相同的种子对其进行初始化,并检查它们是否生成相同的预期20字节长的随机数组:

@Testpublic void testRandomWithSeed() {byte[] seed = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//create and initialize first random generatorSecureRandomNumberGenerator firstGenerator = new SecureRandomNumberGenerator();firstGenerator.setSeed(seed);firstGenerator.setDefaultNextBytesSize(20);//create and initialize second random generatorSecureRandomNumberGenerator secondGenerator = new SecureRandomNumberGenerator();secondGenerator.setSeed(seed);secondGenerator.setDefaultNextBytesSize(20);//generate random bytesByteSource firstRandomBytes = firstGenerator.nextBytes();ByteSource secondRandomBytes = secondGenerator.nextBytes();//compare random arraysassertByteSourcesEquals(firstRandomBytes, secondRandomBytes);//following nextBytes are also the sameByteSource firstNext = firstGenerator.nextBytes();ByteSource secondNext = secondGenerator.nextBytes();//compare random arraysassertByteSourcesEquals(firstRandomBytes, secondRandomBytes);//compare against expected valuesbyte[] expectedRandom = {-116, -31, 67, 27, 13, -26, -38, 96, 122, 31, -67, 73, -52, -4, -22, 26, 18, 22, -124, -24};assertArrayEquals(expectedRandom, firstNext.getBytes());}

散列

哈希 函数将任意长数据作为输入,并将其转换为较小的固定长度数据。 哈希函数的结果称为哈希。 散列是一种操作方式。 无法将哈希转换回原始数据。

要记住的最重要的事情是:始终存储密码哈希而不是密码本身。 永远不要直接存储它。

Shiro提供了两个与哈希相关的接口,都支持安全密码哈希所必需的两个概念:盐析和哈希迭代:

  • Hash -表示哈希算法。
  • Hasher -用此来哈希密码。

盐是散列前与密码连接的随机数组。 它通常与密码一起存储。 没有盐,相同的密码将具有相同的哈希。 这将使密码黑客变得更加容易。

指定许多哈希迭代,以减慢哈希操作的速度。 操作越慢,破解存储密码的难度就越大。 使用很多迭代。

杂凑

哈希接口实现计算哈希函数。 四郎器具六个标准散列函数: MD2 , 被Md5 , SHA1 , SHA256 , SHA3​​84和SHA512 。

每个哈希实现都从ByteSource扩展。 构造函数获取输入数据,盐和所需的迭代次数。 盐和迭代数是可选的。

ByteSource接口方法返回:

  • byte[] getBytes() –哈希,
  • String toBase64() – Base64表示形式的哈希,
  • String toHex() -以十六进制表示形式的哈希。

以下代码不加盐地计算“ Hello Md5”​​文本的Md5哈希值:

@Testpublic void testMd5Hash() {Hash hash = new Md5Hash('Hello Md5');byte[] expectedHash = {-7, 64, 38, 26, 91, 99, 33, 9, 37, 50, -22, -112, -99, 57, 115, -64};assertArrayEquals(expectedHash, hash.getBytes());assertEquals('f940261a5b6321092532ea909d3973c0', hash.toHex());assertEquals('+UAmGltjIQklMuqQnTlzwA==', hash.toBase64());print(hash, 'Md5 with no salt iterations of 'Hello Md5': ');}

下一个代码段用salt计算Sha256的10次迭代:

@Testpublic void testIterationsSha256Hash() {byte[] salt = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};Hash hash = new Sha256Hash('Hello Sha256', salt, 10);byte[] expectedHash = {24, 4, -97, -61, 70, 28, -29, 85, 110, 0, -107, -8, -12, -93, -121, 99, -5, 23, 60, 46, -23, 92, 67, -51, 65, 95, 84, 87, 49, -35, -78, -115};String expectedHex = '18049fc3461ce3556e0095f8f4a38763fb173c2ee95c43cd415f545731ddb28d';String expectedBase64 = 'GASfw0Yc41VuAJX49KOHY/sXPC7pXEPNQV9UVzHdso0=';assertArrayEquals(expectedHash, hash.getBytes());assertEquals(expectedHex, hash.toHex());assertEquals(expectedBase64, hash.toBase64());print(hash, 'Sha256 with salt and 10 iterations of 'Hello Sha256': ');}

比较由框架和客户端代码计算出的迭代 :

@Testpublic void testIterationsDemo() {byte[] salt = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};//iterations computed by the framework Hash shiroIteratedHash = new Sha256Hash('Hello Sha256', salt, 10);//iterations computed by the client code Hash clientIteratedHash = new Sha256Hash('Hello Sha256', salt);for (int i = 1; i < 10; i++) {clientIteratedHash = new Sha256Hash(clientIteratedHash.getBytes());}//compare resultsassertByteSourcesEquals(shiroIteratedHash, clientIteratedHash);}

在幕后:所有具体的哈希类都从SimpleHash扩展,后者将哈希计算委托给JCE MessageDigest实现。 如果您希望使用其他哈希函数扩展Shiro,请直接对其进行实例化。 构造函数将JCE消息摘要(哈希)算法名称作为参数。

哈舍尔

Hasher在哈希函数之上工作,并实现了与盐腌相关的最佳实践。 该接口只有一种方法:

  • HashResponse computeHash(HashRequest request)

哈希请求提供要哈希的字节源和可选的盐。 哈希响应返回哈希和盐。 响应盐不必与提供的盐相同。 更重要的是,它可能不是用于哈希操作的全部盐。

任何哈希器实现均可自由生成自己的随机盐。 仅当请求包含null盐时,默认实现才执行此操作。 另外,用过的盐可以由“碱盐”和“公共盐”组成。 哈希响应中返回“公共盐”。

要了解为什么要用这种方法,必须记得盐通常与密码一起存储。 具有数据库访问权限的攻击者将拥有暴力攻击所需的所有信息。

因此,“公共盐”与密码存储在同一位置,“基本盐”存储在其他位置。 然后,攻击者需要访问两个不同的位置。

默认哈希器是可配置的。 您可以指定基本盐,要使用的迭代次数和哈希算法。 使用任何Shiro哈希实现中的哈希算法名称。 它还总是从哈希请求中返回公共盐。 观看演示 :

@Testpublic void fullyConfiguredHasher() {ByteSource originalPassword = ByteSource.Util.bytes('Secret');byte[] baseSalt = {1, 1, 1, 2, 2, 2, 3, 3, 3};int iterations = 10;DefaultHasher hasher = new DefaultHasher();hasher.setBaseSalt(baseSalt);hasher.setHashIterations(iterations);hasher.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);//custom public saltbyte[] publicSalt = {1, 3, 5, 7, 9};ByteSource salt = ByteSource.Util.bytes(publicSalt);//use hasher to compute password hashHashRequest request = new SimpleHashRequest(originalPassword, salt);HashResponse response = hasher.computeHash(request);byte[] expectedHash = {55, 9, -41, -9, 82, -24, 101, 54, 116, 16, 2, 68, -89, 56, -41, 107, -33, -66, -23, 43, 63, -61, 6, 115, 74, 96, 10, -56, -38, -83, -17, 57};assertArrayEquals(expectedHash, response.getHash().getBytes());}

如果您需要比较密码或数据校验和,请向同一哈希器提供一个“公用盐”。 它将重现哈希操作。 该示例使用Shiro DefaultHasher实现:

@Testpublic void hasherDemo() {ByteSource originalPassword = ByteSource.Util.bytes('Secret');ByteSource suppliedPassword = originalPassword;Hasher hasher = new DefaultHasher();//use hasher to compute password hashHashRequest originalRequest = new SimpleHashRequest(originalPassword);HashResponse originalResponse = hasher.computeHash(originalRequest);//Use salt from originalResponse to compare stored password with user supplied password. We assume that user supplied correct password.HashRequest suppliedRequest = new SimpleHashRequest(suppliedPassword, originalResponse.getSalt());HashResponse suppliedResponse = hasher.computeHash(suppliedRequest);assertEquals(originalResponse.getHash(), suppliedResponse.getHash());//important: the same request hashed twice may lead to different results HashResponse anotherResponse = hasher.computeHash(originalRequest);assertNotSame(originalResponse.getHash(), anotherResponse.getHash());}

注意:由于上面示例中提供的公共盐为null ,因此默认的hashher会生成新的随机公共盐。

加密/解密

密码将数据加密为没有密钥的无法读取的密文。 密码分为两组:对称和不对称。 对称密码使用相同的密钥进行加密和解密。 非对称密码使用两个不同的密钥,一个用于加密,另一个用于解密。

Apache Shiro包含两个对称密码: AES和Blowfish 。 两者都是无状态的,因此是线程安全的。 不支持非对称密码。

两种密码均能够生成随机加密密钥,并且均实现CipherService接口。 该接口定义了两种加密和两种解密方法。 第一组用于字节数组的加密/解密:

  • ByteSource encrypt(byte[] raw, byte[] encryptionKey)
  • ByteSource decrypt(byte[] encrypted, byte[] decryptionKey)

第二组加密/解密流:

  • encrypt(InputStream in, OutputStream out, byte[] encryptionKey)
  • decrypt(InputStream in, OutputStream out, byte[] decryptionKey)

下一个代码段生成新密钥,使用AES密码对秘密消息进行加密,对其进行解密,并将原始消息与解密结果进行比较:

@Testpublic void encryptStringMessage() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();//generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();//encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted = cipher.encrypt(secretBytes, keyBytes);//decrypt the secretbyte[] encryptedBytes = encrypted.getBytes();ByteSource decrypted = cipher.decrypt(encryptedBytes, keyBytes);String secret2 = CodecSupport.toString(decrypted.getBytes());//verify correctnessassertEquals(secret, secret2);}

另一个片段显示了如何使用Blowfish加密/解密流。 Shiro密码不会关闭也不刷新输入或输出流。 您必须自己做:

@Testpublic void encryptStream() {InputStream secret = openSecretInputStream();BlowfishCipherService cipher = new BlowfishCipherService();// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretOutputStream encrypted = openSecretOutputStream();try {cipher.encrypt(secret, encrypted, keyBytes);} finally {// The cipher does not flush neither close streams.closeStreams(secret, encrypted);}// decrypt the secretInputStream encryptedInput = convertToInputStream(encrypted);OutputStream decrypted = openSecretOutputStream();try {cipher.decrypt(encryptedInput, decrypted, keyBytes);} finally {// The cipher does not flush neither close streams.closeStreams(secret, encrypted);}// verify correctnessassertStreamsEquals(secret, decrypted);}

如果使用相同的密钥两次加密相同的文本 ,则会得到两个不同的加密文本:

@Testpublic void unpredictableEncryptionProof() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt two timesbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted1 = cipher.encrypt(secretBytes, keyBytes);ByteSource encrypted2 = cipher.encrypt(secretBytes, keyBytes);// verify correctnessassertArrayNotSame(encrypted1.getBytes(), encrypted2.getBytes());}

前面的两个示例都使用Key generateNewKey()方法生成密钥。 使用方法setKeySize(int keySize)覆盖默认密钥大小(128位)。 或者,方法Key generateNewKey(int keyBitSize)的方法的keyBitSize参数以位为单位指定密钥大小。

有些密码仅支持某些密钥大小。 例如 ,AES仅支持128、192和256位日志密钥:

@Test(expected=RuntimeException.class)public void aesWrongKeySize() {AesCipherService cipher = new AesCipherService();//The call throws an exception. Aes supports only keys of 128, 192, and 256 bits.cipher.generateNewKey(200);}@Testpublic void aesGoodKeySize() {AesCipherService cipher = new AesCipherService();//aes supports only keys of 128, 192, and 256 bitscipher.generateNewKey(128);cipher.generateNewKey(192);cipher.generateNewKey(256);}

就基础而言,就是这样。 您不需要更多的内容来加密和解密应用程序中的敏感数据。

更新:我对此过于乐观。 了解更多信息总是有用的,尤其是在处理敏感数据时。 此方法大部分是但不是完全安全的。 问题和解决方案都在我的另一篇文章中进行了介绍。

加密/解密-高级

上一章介绍了如何加密和解密某些数据。 本章将进一步介绍Shiro加密的工作原理以及如何对其进行自定义。 它还显示了如果标准的两个密码不适合您,那么如何轻松添加新密码。

初始化向量

初始化向量是在加密期间使用的随机生成的字节数组。 使用初始化向量的密码很难预测,因此对于攻击者来说很难解密。

Shiro自动生成初始化向量并将其用于加密数据。 然后将矢量与加密数据连接起来并返回给客户端代码。 您可以通过在密码上调用setGenerateInitializationVectors(false)将其关闭。 该方法在JcaCipherService类上定义。 这两个默认的加密类都对其进行了扩展。

初始化向量大小是特定于加密算法的。 如果默认大小(128位)不起作用,请使用setInitializationVectorSize方法对其进行自定义。

随机发生器

关闭初始化向量不一定意味着密码变得可预测。 河豚和AES都具有随机性。

以下示例关闭了初始化向量,但是加密的文本仍然不同:

@Testpublic void unpredictableEncryptionNoIVProof() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();cipher.setGenerateInitializationVectors(false);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt two timesbyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted1 = cipher.encrypt(secretBytes, keyBytes);ByteSource encrypted2 = cipher.encrypt(secretBytes, keyBytes);// verify correctnessassertArrayNotSame(encrypted1.getBytes(), encrypted2.getBytes());}

可以自定义或关闭随机性。 但是,永远不要在生产代码中这样做。 随机性是安全数据加密的绝对必要条件。

两种Shiro加密算法都从JcaCipherService类扩展。 该类具有setSecureRandom(SecureRandom secureRandom)方法。 安全随机数是标准的Java JCE随机数生成器。 扩展它以创建自己的实现,并将其传递给密码。

我们的SecureRandom ConstantSecureRandom实现始终返回零。 我们将其提供给密码并关闭了初始化向量,以创建不安全的可预测加密 :

@Testpublic void predictableEncryption() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();cipher.setSecureRandom(new ConstantSecureRandom());cipher.setGenerateInitializationVectors(false);// define the keybyte[] keyBytes = {5, -112, 36, 113, 80, -3, -114, 77, 38, 127, -1, -75, 65, -102, -13, -47};// encrypt first timebyte[] secretBytes = CodecSupport.toBytes(secret);ByteSource encrypted = cipher.encrypt(secretBytes, keyBytes);// verify correctness, the result is always the samebyte[] expectedBytes = {76, 69, -49, -110, -121, 97, -125, -111, -11, -61, 61, 11, -40, 26, -68, -58};assertArrayEquals(expectedBytes, encrypted.getBytes());}

持续安全的随机实现是漫长而无趣的。 它在Github上可用 。

自定义密码

开箱即用Shiro仅提供Blowfish和AES加密方法。 该框架没有实现自己的算法。 而是将加密委托给JCE类。

Shiro仅提供安全的默认设置和更简单的API。 这种设计使得可以使用任何JCE分组密码来扩展Shiro。

块密码对每个块的消息进行加密。 所有块具有相等的固定大小。 如果最后一个块太短,则添加填充以使其与其他所有块相同。 每个块被加密并与先前加密的块组合。

因此,您必须配置:

  • 加密方法
  • 块大小
  • 填充
  • 如何结合块 。

加密方式

自定义密码扩展了DefaultBlockCipherService类。 该类只有一个带有一个参数的构造函数:算法名称。 您可以提供任何与JCE兼容的算法名称 。

例如,这是Shiro AES密码的源代码:

public class AesCipherService extends DefaultBlockCipherService {private static final String ALGORITHM_NAME = 'AES';public AesCipherService() {super(ALGORITHM_NAME);}}

AES不需要指定其他加密参数(块大小,填充,加密方法)。 对于AES,默认值足够好。

块大小

默认的块密码服务有两种方法可以自定义块大小。 setBlockSize(int blockSize)方法仅适用于字节数组的编码和解码。 setStreamingBlockSize(int streamingBlockSize)方法仅适用于流编码和解码。

0表示将使用默认算法特定的块大小。 这是默认值。

分组密码块大小是特定于算法的。 选定的加密算法可能不适用于任意块大小 :

@Test(expected=CryptoException.class)public void aesWrongBlockSize() {String secret = 'Tell nobody!';AesCipherService cipher = new AesCipherService();// set wrong block size cipher.setBlockSize(200);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);cipher.encrypt(secretBytes, keyBytes);}

填充

使用方法setPaddingScheme(PaddingScheme paddingScheme)指定字节数组加密和解密填充。 setStreamingPaddingScheme( PaddingScheme paddingScheme)指定流加密和解密填充。

枚举PaddingScheme代表所有典型的填充方案。 默认情况下,并非所有功能都可用,您可能必须安装自定义JCE提供程序才能使用它们。

null表示将使用默认算法特定的填充。 这是默认值。

如果需要PaddingScheme枚举中未包含的填充,请使用setPaddingSchemeNamesetStreamingPaddingSchemeName方法。 这些方法采用带有填充方案名称作为参数的字符串。 它们的类型安全性较上一类小,但更灵活。

填充非常特定于算法。 选定的加密算法可能不适用于任意填充 :

@Test(expected=CryptoException.class)public void aesWrongPadding() {String secret = 'Tell nobody!';BlowfishCipherService cipher = new BlowfishCipherService();// set wrong block size cipher.setPaddingScheme(PaddingScheme.PKCS1);// generate key with default 128 bits sizeKey key = cipher.generateNewKey();byte[] keyBytes = key.getEncoded();// encrypt the secretbyte[] secretBytes = CodecSupport.toBytes(secret);cipher.encrypt(secretBytes, keyBytes);}

操作模式

操作模式指定块如何链接(组合)在一起。 与填充方案一样,您可以使用OperationMode枚举或字符串来提供它们。

注意,并非每种操作模式都可用。 此外,他们并非天生平等。 某些链接模式不如其他链接模式安全。 默认的密码反馈操作模式既安全又适用于所有JDK环境。

设置字节数组加密和解密操作模式的方法:

  • setMode(OperationMode mode)
  • setModeName(String modeName)

设置流加密和解密操作模式的方法:

  • setStreamingMode(OperationMode mode)
  • setStreamingModeName(String modeName)

练习–解密Openssl

假设应用程序发送使用Linux openssl命令加密的数据。 我们知道用于加密数据的密钥和命令的十六进制表示形式:

  • 密钥: B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A
  • 命令: openssl des3 -base64 -p -K <secret key> -iv <initialization vector>

每个消息都包含初始化向量的十六进制表示形式和base64编码的加密消息。

样本消息:

  • 初始化向量: F758CEEB7CA7E188
  • 消息: GmfvxhbYJbVFT8Ad1Xc+Gh38OBmhzXOV

使用OpenSSL生成样本

该示例消息已使用以下命令加密:

#encrypt 'yeahh, that worked!'
echo yeahh, that worked! | openssl des3 -base64 -p -K B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A -iv F758CEEB7CA7E188

使用OpenSSL选项-P可以生成密钥或随机初始向量。

首先,我们必须找出算法名称,填充和操作模式。 幸运的是,这三个文件都可以在OpenSSL 文档中找到 。 Des3 是 CBC模式下三重DES加密算法的别名 ,而OpenSSL 使用 PKCS#5填充。

密码块链接 (CBC)需要与块大小相同大小的初始化向量。 三重DES需要64位长的块。 Java JCE对Triple DES 使用“ DESede”算法名称。

我们的自定义密码扩展并配置了DefaultBlockCipherService

public class OpensslDes3CipherService extends DefaultBlockCipherService {public OpensslDes3CipherService() {super('DESede');setMode(OperationMode.CBC);setPaddingScheme(PaddingScheme.PKCS5);setInitializationVectorSize(64);}}

Shiro密码decrypt方法需要两个输入字节数组,密文和密钥。 密文应同时包含初始化向量和加密密文。 因此,在尝试解密消息之前,我们必须将它们组合在一起。 该方法combine 两个数组合并为一个:

private byte[] combine(byte[] iniVector, byte[] ciphertext) {byte[] ivCiphertext = new byte[iniVector.length + ciphertext.length];System.arraycopy(iniVector, 0, ivCiphertext, 0, iniVector.length);System.arraycopy(ciphertext, 0, ivCiphertext, iniVector.length, ciphertext.length);return ivCiphertext;}

实际的解密看起来通常是 :

@Testpublic void opensslDes3Decryption() {String hexInitializationVector = 'F758CEEB7CA7E188';String base64Ciphertext = 'GmfvxhbYJbVFT8Ad1Xc+Gh38OBmhzXOV';String hexSecretKey = 'B9FAB84B65870109A6E8707BC95151C245BF18204C028A6A';//decode secret message and initialization vectorbyte[] iniVector = Hex.decode(hexInitializationVector);byte[] ciphertext = Base64.decode(base64Ciphertext);//combine initialization vector and ciphertext togetherbyte[] ivCiphertext = combine(iniVector, ciphertext);//decode secret keybyte[] keyBytes = Hex.decode(hexSecretKey);//initialize cipher and decrypt the messageOpensslDes3CipherService cipher = new OpensslDes3CipherService();ByteSource decrypted = cipher.decrypt(ivCiphertext, keyBytes);//verify resultString theMessage = CodecSupport.toString(decrypted.getBytes());assertEquals('yeahh, that worked!\n', theMessage);}

结束

Apache Shiro教程的这一部分介绍了1.2版中可用的加密功能。 所有使用的示例都可以在Github上找到 。

参考: Apache Shiro第3部分–来自JCG合作伙伴 Maria Jurcovicova的“ 密码”,来自This is Stuff博客。

翻译自: https://www.javacodegeeks.com/2012/05/apache-shiro-part-3-cryptography.html

Apache Shiro第3部分–密码学相关推荐

  1. apache shiro_Apache Shiro第3部分–密码学

    apache shiro 除了保护网页和管理访问权限外, Apache Shiro还执行基本的加密任务. 该框架能够: 加密和解密数据, 哈希数据, 生成随机数. Shiro没有实现任何加密算法. 所 ...

  2. 分布式平台下的HS(High-Security) --Apache Shiro API(介绍)

    在尝试保护你的应用时,你是否有过挫败感?是否觉得现有的Java安全解决方案难以使用,只会让你更糊涂?本文介绍的Apache Shiro,是一个不同寻常的Java安全框架,为保护应用提供了简单而强大的方 ...

  3. Apache shiro介绍

    原文链接Application Security With Apache Shiro(翻译) 前面自己自己配合谷歌翻译,后面大部分谷歌翻译. 当你尝试保护你的程序时候你会被困扰吗?你会觉得现有的jav ...

  4. Apache Shiro第1部分–基础

    Apache Shiro (最初称为JSecurity)是Java安全框架. 它被接受并于2010年成为Apache顶级项目.它的目标是功能强大且易于使用. 该项目正在积极开发中,用户和开发人员的邮件 ...

  5. Apache Shiro入门

    Apache Shiro入门 @(Shiro)[shiro,安全框架] Apache Shiro入门 Apache Shiro基本概述 Apache Shiro基本概念 使用Shiro能做什么 Shi ...

  6. Apache shiro 权限

    2021SC@SDUSC 文档目录 Apache shiro学习文档 第一章 Apache Shiro 概述 第二章 Apache Shiro 架构 第三章 Apache Shiro 配置 3.1 配 ...

  7. 让 Apache Shiro 保护你的应用

    让 Apache Shiro 保护你的应用 作者:Les Hazlewood 译者:胡伟红 查看英文原文: Application Security With Apache Shiro 在尝试保护你的 ...

  8. 让Apache Shiro保护你的应用

    目录 什么是Apache Shiro? 为何要创建Apache Shiro? 今天,你为何愿意使用Apache Shiro? 谁在用Shiro? 核心概念:Subject,SecurityManage ...

  9. 【漏洞复现】Apache Shiro 反序列化漏洞

    Apache Shiro 反序列化漏洞 一.简介 二.环境 三.漏洞原理 四.AES秘钥 1.判断AES秘钥 五.Shiro rememberMe反序列化漏洞(Shiro-550) 1.版本1.4.2 ...

最新文章

  1. 想挖矿?不如先学习一下以太坊
  2. 【Prometheus】PromQL 万字详解
  3. mysql ora01031_ORA-01031:insufficient privileges解决方法
  4. 全网最细节的sds讲解,从理论到实践!
  5. 【OpenGL】十三、OpenGL 绘制三角形 ( 绘制单个三角形 | 三角形绘制顺序 | 绘制多个三角形 )
  6. java中构造方法和方法全面解析
  7. hdu 3932Groundhog Build Home 【爬山算法】
  8. 小余学调度:学习记录(2021年12月)
  9. 开发高性能的ADO.NET应用
  10. 如何解决Ubuntu 12.04(64位)系统在virtualbox环境下无法开机自动挂载共享目录的问题
  11. 胎压监测 (10 分)
  12. android touch事件坐标原点,图解Android:Touch事件传递机制
  13. 正则表达式去掉文件路径中的特殊字符
  14. java实验小狗状态_Java第6章-接口与多态实验2(小狗的状态)
  15. java输入、输出流的简单入门
  16. 电梯plc的io分配_用PLC构成液体混合控制系统IO分配及梯形图编程
  17. Unity Compute Shader入门(大量对象随机赋值颜色实验)
  18. Retrying connect to server: 192.168.10.101/192.168.10.101:10020. Already tried 0 time(s); retry poli
  19. HTTP tunneling is disabled问题 解决
  20. 安装vs2013出现此预许可证已过期--解决办法

热门文章

  1. 对象包装器与自动(拆箱)装箱+参数数量可变的方法+枚举类
  2. jdk 版本和内部版本对应_JDK 14 Rampdown:内部版本27
  3. java分布式系统开发_从微服务到分布式系统-Java开发人员生存指南
  4. 同步代码和异步代码_告别异步代码
  5. java面试解决项目难题_Java转换难题者,不适合工作(或面试)
  6. javaone_代理的JavaOne 2014观察
  7. Kogito,ergo规则—第2部分:规则的全面执行模型
  8. php cdi_使用CDI的InjectionPoint注入配置值
  9. ddl hibernate_Hibernate:DDL模式生成
  10. java erlang_Java开发人员的Erlang