Skip to content

Commit a801878

Browse files
committed
Updates the default Cipher mode to GCM in AesCipherService
Adds tests for each mode Fix issue where GCM needs a different AlgorithmParameterSpec implementation (previously IvParameterSpec was used)
1 parent 92bfac6 commit a801878

5 files changed

Lines changed: 179 additions & 46 deletions

File tree

crypto/cipher/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@
6060
<groupId>org.apache.shiro</groupId>
6161
<artifactId>shiro-crypto-core</artifactId>
6262
</dependency>
63+
64+
<dependency>
65+
<groupId>org.bouncycastle</groupId>
66+
<artifactId>bcprov-jdk15on</artifactId>
67+
<version>1.64</version>
68+
<scope>test</scope>
69+
</dependency>
6370
</dependencies>
6471

6572
</project>

crypto/cipher/src/main/java/org/apache/shiro/crypto/AesCipherService.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@
1818
*/
1919
package org.apache.shiro.crypto;
2020

21+
import javax.crypto.spec.GCMParameterSpec;
22+
import java.security.spec.AlgorithmParameterSpec;
23+
2124
/**
2225
* {@code CipherService} using the {@code AES} cipher algorithm for all encryption, decryption, and key operations.
2326
* <p/>
2427
* The AES algorithm can support key sizes of {@code 128}, {@code 192} and {@code 256} bits<b>*</b>. This implementation
2528
* defaults to 128 bits.
2629
* <p/>
27-
* Note that this class retains the parent class's default {@link OperationMode#CBC CBC} mode of operation
30+
* Note that this class retains changes the parent class's default {@link OperationMode#CBC CBC} mode to {@link OperationMode#GCM GCM} of operation
2831
* instead of the typical JDK default of {@link OperationMode#ECB ECB}. {@code ECB} should not be used in
2932
* security-sensitive environments because {@code ECB} does not allow for initialization vectors, which are
3033
* considered necessary for strong encryption. See the {@link DefaultBlockCipherService parent class}'s JavaDoc and the
@@ -59,7 +62,7 @@ public class AesCipherService extends DefaultBlockCipherService {
5962
* </tr>
6063
* <tr>
6164
* <td>{@link #setMode mode}</td>
62-
* <td>{@link OperationMode#CBC CBC}<b>*</b></td>
65+
* <td>{@link OperationMode#GCM GCM}<b>*</b></td>
6366
* </tr>
6467
* <tr>
6568
* <td>{@link #setPaddingScheme paddingScheme}</td>
@@ -75,16 +78,28 @@ public class AesCipherService extends DefaultBlockCipherService {
7578
* </tr>
7679
* </table>
7780
* <p/>
78-
* <b>*</b> The {@link OperationMode#CBC CBC} operation mode is used instead of the JDK default {@code ECB} to
81+
* <b>*</b> The {@link OperationMode#GCM GCM} operation mode is used instead of the JDK default {@code ECB} to
7982
* ensure strong encryption. {@code ECB} should not be used in security-sensitive environments - see the
8083
* {@link DefaultBlockCipherService DefaultBlockCipherService} class JavaDoc's &quot;Operation Mode&quot; section
8184
* for more.
8285
* <p/>
83-
* <b>**</b>In conjunction with the default {@code CBC} operation mode, initialization vectors are generated by
86+
* <b>**</b>In conjunction with the default {@code GCM} operation mode, initialization vectors are generated by
8487
* default to ensure strong encryption. See the {@link JcaCipherService JcaCipherService} class JavaDoc for more.
8588
*/
8689
public AesCipherService() {
8790
super(ALGORITHM_NAME);
91+
setMode(OperationMode.GCM);
92+
setStreamingMode(OperationMode.GCM);
8893
}
8994

95+
@Override
96+
protected AlgorithmParameterSpec createParameterSpec(byte[] iv, boolean streaming) {
97+
98+
if ((streaming && OperationMode.GCM.name().equals(getStreamingModeName()))
99+
|| (!streaming && OperationMode.GCM.name().equals(getModeName()))) {
100+
return new GCMParameterSpec(getKeySize(), iv);
101+
}
102+
103+
return super.createParameterSpec(iv, streaming);
104+
}
90105
}

crypto/cipher/src/main/java/org/apache/shiro/crypto/JcaCipherService.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,13 +590,18 @@ private javax.crypto.Cipher initNewCipher(int jcaCipherMode, byte[] key, byte[]
590590

591591
javax.crypto.Cipher cipher = newCipherInstance(streaming);
592592
java.security.Key jdkKey = new SecretKeySpec(key, getAlgorithmName());
593-
IvParameterSpec ivSpec = null;
593+
AlgorithmParameterSpec ivSpec = null;
594+
594595
if (iv != null && iv.length > 0) {
595-
ivSpec = new IvParameterSpec(iv);
596+
ivSpec = createParameterSpec(iv, streaming);
596597
}
597598

598599
init(cipher, jcaCipherMode, jdkKey, ivSpec, getSecureRandom());
599600

600601
return cipher;
601602
}
603+
604+
protected AlgorithmParameterSpec createParameterSpec(byte[] iv, boolean streaming) {
605+
return new IvParameterSpec(iv);
606+
}
602607
}

crypto/cipher/src/test/groovy/org/apache/shiro/crypto/AesCipherServiceTest.groovy

Lines changed: 132 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
*/
1919
package org.apache.shiro.crypto
2020

21+
import org.bouncycastle.jce.provider.BouncyCastleProvider
22+
23+
import java.security.Security
24+
25+
import static org.junit.Assert.*;
26+
2127
import org.apache.shiro.codec.CodecSupport
2228
import org.apache.shiro.util.ByteSource
2329
import org.junit.Test
@@ -29,46 +35,146 @@ import static junit.framework.Assert.*
2935
*
3036
* @since 1.0
3137
*/
32-
public class AesCipherServiceTest {
38+
class AesCipherServiceTest {
3339

3440
private static final String[] PLAINTEXTS = [
3541
"Hello, this is a test.",
3642
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
37-
];
43+
]
44+
45+
AesCipherServiceTest() {
46+
Security.addProvider(new BouncyCastleProvider())
47+
}
3848

3949
@Test
40-
public void testBlockOperations() {
41-
AesCipherService aes = new AesCipherService();
50+
void testBlockOperations() {
51+
AesCipherService cipher = new AesCipherService()
52+
assertBlock(cipher)
53+
}
4254

43-
byte[] key = aes.generateNewKey().getEncoded();
55+
@Test
56+
void testStreamingOperations() {
57+
AesCipherService cipher = new AesCipherService()
58+
assertStreaming(cipher)
59+
}
4460

45-
for (String plain : PLAINTEXTS) {
46-
byte[] plaintext = CodecSupport.toBytes(plain);
47-
ByteSource ciphertext = aes.encrypt(plaintext, key);
48-
ByteSource decrypted = aes.decrypt(ciphertext.getBytes(), key);
49-
assertTrue(Arrays.equals(plaintext, decrypted.getBytes()));
50-
}
61+
@Test
62+
void testAesGcm() {
63+
assertBlock(OperationMode.GCM)
64+
assertStreaming(OperationMode.GCM)
5165
}
5266

5367
@Test
54-
public void testStreamingOperations() {
68+
void testCcm() {
69+
assertBlock(OperationMode.CCM, PaddingScheme.NONE, 13 * 8) // 13 bytes
70+
assertStreaming(OperationMode.CCM)
71+
}
5572

56-
AesCipherService cipher = new AesCipherService();
57-
byte[] key = cipher.generateNewKey().getEncoded();
73+
@Test
74+
void testCfb() {
75+
assertBlock(OperationMode.CFB)
76+
assertStreaming(OperationMode.CFB)
77+
}
5878

79+
@Test
80+
void testCtr() {
81+
assertBlock(OperationMode.CTR)
82+
assertStreaming(OperationMode.CTR)
83+
}
84+
85+
@Test
86+
void testEax() {
87+
assertBlock(OperationMode.EAX)
88+
assertStreaming(OperationMode.EAX)
89+
}
90+
91+
@Test
92+
void testEcb() {
93+
assertBlock(OperationMode.ECB, PaddingScheme.PKCS5)
94+
}
95+
96+
@Test
97+
void testNone() {
98+
assertBlock((OperationMode) null, null)
99+
}
100+
101+
@Test
102+
void testOcb() {
103+
assertBlock(OperationMode.OCB, PaddingScheme.NONE, 15 * 8) // 15 bytes
104+
assertStreaming(OperationMode.OCB, PaddingScheme.NONE, 16 * 8) // 16 bytes
105+
}
106+
107+
@Test
108+
void testOfb() {
109+
assertBlock(OperationMode.OFB)
110+
assertStreaming(OperationMode.OFB)
111+
}
112+
113+
@Test
114+
void testPcbc() {
115+
assertBlock(OperationMode.PCBC, PaddingScheme.PKCS5)
116+
assertStreaming(OperationMode.PCBC, PaddingScheme.PKCS5)
117+
}
118+
119+
private static assertBlock(OperationMode mode, PaddingScheme scheme = PaddingScheme.NONE, int ivSize = JcaCipherService.DEFAULT_KEY_SIZE) {
120+
AesCipherService cipher = new AesCipherService()
121+
cipher.setInitializationVectorSize(ivSize)
122+
123+
if (mode == null) {
124+
cipher.setModeName(null)
125+
} else {
126+
cipher.setMode(mode)
127+
}
128+
129+
if (scheme == null) {
130+
cipher.setPaddingSchemeName(null)
131+
} else {
132+
cipher.setPaddingScheme(scheme)
133+
}
134+
assertBlock(cipher)
135+
}
136+
137+
private static assertStreaming(OperationMode mode, PaddingScheme scheme = PaddingScheme.NONE, int ivSize = JcaCipherService.DEFAULT_KEY_SIZE) {
138+
AesCipherService cipher = new AesCipherService()
139+
cipher.setInitializationVectorSize(ivSize)
140+
141+
if (mode == null) {
142+
cipher.setStreamingModeName(null)
143+
} else {
144+
cipher.setStreamingMode(mode)
145+
}
146+
147+
if (scheme == null) {
148+
cipher.setStreamingPaddingScheme(null)
149+
} else {
150+
cipher.setStreamingPaddingScheme(scheme)
151+
}
152+
assertBlock(cipher)
153+
}
154+
155+
private static assertBlock(AesCipherService cipher, byte[] key = cipher.generateNewKey().getEncoded()) {
59156
for (String plain : PLAINTEXTS) {
60-
byte[] plaintext = CodecSupport.toBytes(plain);
61-
InputStream plainIn = new ByteArrayInputStream(plaintext);
62-
ByteArrayOutputStream cipherOut = new ByteArrayOutputStream();
63-
cipher.encrypt(plainIn, cipherOut, key);
64-
65-
byte[] ciphertext = cipherOut.toByteArray();
66-
InputStream cipherIn = new ByteArrayInputStream(ciphertext);
67-
ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
68-
cipher.decrypt(cipherIn, plainOut, key);
69-
70-
byte[] decrypted = plainOut.toByteArray();
71-
assertTrue(Arrays.equals(plaintext, decrypted));
157+
byte[] plaintext = CodecSupport.toBytes(plain)
158+
ByteSource ciphertext = cipher.encrypt(plaintext, key)
159+
ByteSource decrypted = cipher.decrypt(ciphertext.getBytes(), key)
160+
assertTrue(Arrays.equals(plaintext, decrypted.getBytes()))
161+
}
162+
}
163+
164+
private static assertStreaming(AesCipherService cipher, byte[] key = cipher.generateNewKey().getEncoded()) {
165+
for (String plain : PLAINTEXTS) {
166+
byte[] plaintext = CodecSupport.toBytes(plain)
167+
InputStream plainIn = new ByteArrayInputStream(plaintext)
168+
ByteArrayOutputStream cipherOut = new ByteArrayOutputStream()
169+
cipher.encrypt(plainIn, cipherOut, key)
170+
171+
byte[] ciphertext = cipherOut.toByteArray()
172+
InputStream cipherIn = new ByteArrayInputStream(ciphertext)
173+
ByteArrayOutputStream plainOut = new ByteArrayOutputStream()
174+
cipher.decrypt(cipherIn, plainOut, key)
175+
176+
byte[] decrypted = plainOut.toByteArray()
177+
assertTrue(Arrays.equals(plaintext, decrypted))
72178
}
73179
}
74180
}

web/src/test/java/org/apache/shiro/web/mgt/CookieRememberMeManagerTest.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,13 @@ public void getRememberedPrincipals() {
125125

126126
//The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test.
127127
//This will have to change any time the PrincipalCollection implementation changes:
128-
final String userPCAesBase64 = "WlD5MLzzZznN3dQ1lPJO/eScSuY245k29aECNmjUs31o7Yu478hWhaM5Sj" +
129-
"jmoe900/72JNu3hcJaPG6Q17Vuz4F8x0kBjbFnPVx4PqzsZYT6yreeS2jwO6OwfI+efqXOKyB2a5KPtnr" +
130-
"7jt5kZsyH38XJISb81cf6xqTGUru8zC+kNqJFz7E5RpO0kraBofS5jhMm45gDVjDRkjgPJAzocVWMtrza" +
131-
"zy67P8eb+kMSBCqGI251JTNAGboVgQ28KjfaAJ/6LXRJUj7kB7CGia7mgRk+hxzEJGDs81at5VOPqODJr" +
132-
"xb8tcIdemFUFIkiYVP9bGs4dP3ECtmw7aNrCzv+84sx3vRFUrd5DbDYpEuE12hF2Y9owDK9sxStbXoF0y" +
133-
"A32dhfGDIqS+agsass0sWn8WX2TM9i8SxrUjiFbxqyIG49HbqGrZp5QLM9IuIwO+TzGfF1FzumQGdwmWT" +
134-
"xkVapw5UESl34YvA615cb+82ue1I=";
128+
final String userPCAesBase64 = "0o6DCfePYTjK4q579qzUFEfkeGRvbBOdKHp2y8/nGAltt1Vz8uW0Z8igeO" +
129+
"Tq/yBmcw25f3Q0ui/Leg3x0iQZWhw9Bbu0mFHmHsGxEd6mPwtUpSegIjyX5c/kZpqnb7QLdajPWiczX8P" +
130+
"Oc2Eku5+8ye1u38Y8uKlklHxcYCPh0pRiDSBxfjPsLaDfOpGbmPjZd4SVg68i/++TvUjqBNJyb+pDix3f" +
131+
"PeuPvReWGcE50iovezVZrEfDOAQ0cZYW35ShypMWOmE9yZnb+p8++StDyAUegryyuIa4pjuRzfMh9D+sN" +
132+
"F9tm/EnDC1VCer2S/a0AGlWAQiM7jrWt1sNinZcKIrvShaWI21tONJt8WhozNS2H72lk4p92rfLNHeglT" +
133+
"xObxIYxLfTI9KiToSe1nYmpQmbBO8x1wWDkWBG//EqRvhgbIfQVqJp12T0fJC1nFuZuVhw/ZanaAZGDk8" +
134+
"7aLMiw3T6FBZtWaspgvfH+0TJrTD8Ra386ekNXNN8JW8=";
135135

136136
Cookie[] cookies = new Cookie[]{
137137
new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)
@@ -165,13 +165,13 @@ public void getRememberedPrincipalsNoMoreDefaultCipher() {
165165

166166
//The following base64 string was determined from the log output of the above 'onSuccessfulLogin' test.
167167
//This will have to change any time the PrincipalCollection implementation changes:
168-
final String userPCAesBase64 = "WlD5MLzzZznN3dQ1lPJO/eScSuY245k29aECNmjUs31o7Yu478hWhaM5Sj" +
169-
"jmoe900/72JNu3hcJaPG6Q17Vuz4F8x0kBjbFnPVx4PqzsZYT6yreeS2jwO6OwfI+efqXOKyB2a5KPtnr" +
170-
"7jt5kZsyH38XJISb81cf6xqTGUru8zC+kNqJFz7E5RpO0kraBofS5jhMm45gDVjDRkjgPJAzocVWMtrza" +
171-
"zy67P8eb+kMSBCqGI251JTNAGboVgQ28KjfaAJ/6LXRJUj7kB7CGia7mgRk+hxzEJGDs81at5VOPqODJr" +
172-
"xb8tcIdemFUFIkiYVP9bGs4dP3ECtmw7aNrCzv+84sx3vRFUrd5DbDYpEuE12hF2Y9owDK9sxStbXoF0y" +
173-
"A32dhfGDIqS+agsass0sWn8WX2TM9i8SxrUjiFbxqyIG49HbqGrZp5QLM9IuIwO+TzGfF1FzumQGdwmWT" +
174-
"xkVapw5UESl34YvA615cb+82ue1I=";
168+
final String userPCAesBase64 = "0o6DCfePYTjK4q579qzUFEfkeGRvbBOdKHp2y8/nGAltt1Vz8uW0Z8igeO" +
169+
"Tq/yBmcw25f3Q0ui/Leg3x0iQZWhw9Bbu0mFHmHsGxEd6mPwtUpSegIjyX5c/kZpqnb7QLdajPWiczX8P" +
170+
"Oc2Eku5+8ye1u38Y8uKlklHxcYCPh0pRiDSBxfjPsLaDfOpGbmPjZd4SVg68i/++TvUjqBNJyb+pDix3f" +
171+
"PeuPvReWGcE50iovezVZrEfDOAQ0cZYW35ShypMWOmE9yZnb+p8++StDyAUegryyuIa4pjuRzfMh9D+sN" +
172+
"F9tm/EnDC1VCer2S/a0AGlWAQiM7jrWt1sNinZcKIrvShaWI21tONJt8WhozNS2H72lk4p92rfLNHeglT" +
173+
"xObxIYxLfTI9KiToSe1nYmpQmbBO8x1wWDkWBG//EqRvhgbIfQVqJp12T0fJC1nFuZuVhw/ZanaAZGDk8" +
174+
"7aLMiw3T6FBZtWaspgvfH+0TJrTD8Ra386ekNXNN8JW8=";
175175

176176
Cookie[] cookies = new Cookie[]{
177177
new Cookie(CookieRememberMeManager.DEFAULT_REMEMBER_ME_COOKIE_NAME, userPCAesBase64)

0 commit comments

Comments
 (0)