From 85719fcd6473ef167e58634f2e5cf1fce900b850 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Mon, 8 May 2017 18:58:33 +0200 Subject: [PATCH] Use Base64 implementation provided by Java 8 --- .../configurers/NamespaceHttpJeeTests.groovy | 3 +- .../NamespaceHttpLogoutTests.groovy | 3 +- .../http/InterceptUrlConfigTests.groovy | 5 +- .../config/http/NamespaceHttpBasicTests.java | 6 +- .../encoding/LdapShaPasswordEncoder.java | 6 +- .../encoding/Md4PasswordEncoder.java | 5 +- .../MessageDigestPasswordEncoder.java | 6 +- .../KeyBasedPersistenceTokenService.java | 8 +- .../security/crypto/codec/Base64.java | 646 ------------------ .../crypto/scrypt/SCryptPasswordEncoder.java | 373 +++++----- .../security/crypto/codec/Base64Tests.java | 51 -- .../authentication/DefaultStateGenerator.java | 5 +- ...ationSimpleHttpInvokerRequestExecutor.java | 4 +- .../SecurityMockMvcRequestPostProcessors.java | 10 +- .../AbstractRememberMeServices.java | 15 +- ...ersistentTokenBasedRememberMeServices.java | 8 +- .../www/BasicAuthenticationFilter.java | 4 +- .../www/DigestAuthenticationEntryPoint.java | 4 +- .../www/DigestAuthenticationFilter.java | 9 +- 19 files changed, 240 insertions(+), 931 deletions(-) delete mode 100644 crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java delete mode 100644 crypto/src/test/java/org/springframework/security/crypto/codec/Base64Tests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.groovy index 2a8e5ef279..8992110218 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.FilterInvocation diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.groovy index 8e3edfb265..59789634af 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ import org.springframework.security.config.annotation.web.configuration.BaseWebC import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextImpl -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.AuthenticationEntryPoint import org.springframework.security.web.FilterChainProxy import org.springframework.security.web.FilterInvocation diff --git a/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy index 457f09da67..01c4c2ac6f 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/InterceptUrlConfigTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,6 @@ import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse import org.springframework.mock.web.MockServletContext import org.springframework.security.access.SecurityConfig -import org.springframework.security.crypto.codec.Base64 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -383,7 +382,7 @@ class InterceptUrlConfigTests extends AbstractHttpConfigTests { def login(MockHttpServletRequest request, String username, String password) { String toEncode = username + ':' + password - request.addHeader('Authorization','Basic ' + new String(Base64.encode(toEncode.getBytes('UTF-8')))) + request.addHeader('Authorization','Basic ' + Base64.encoder.encodeToString(toEncode.getBytes('UTF-8'))) } @RestController diff --git a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java b/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java index 5f5d186d8e..0e63ca76f5 100644 --- a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java +++ b/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.security.config.http; import java.lang.reflect.Method; +import java.util.Base64; import javax.servlet.Filter; import javax.servlet.http.HttpServletResponse; @@ -31,7 +32,6 @@ import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.crypto.codec.Base64; import static org.assertj.core.api.Assertions.assertThat; @@ -87,7 +87,7 @@ public class NamespaceHttpBasicTests { // @formatter:on this.request.addHeader("Authorization", - "Basic " + new String(Base64.encode("user:test".getBytes("UTF-8")))); + "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java index 5ba2fff772..9fda7edc24 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java @@ -17,8 +17,8 @@ package org.springframework.security.authentication.encoding; import java.security.MessageDigest; +import java.util.Base64; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.crypto.codec.Utf8; import org.springframework.util.Assert; @@ -107,13 +107,13 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX; } - return prefix + Utf8.decode(Base64.encode(hash)); + return prefix + Utf8.decode(Base64.getEncoder().encode(hash)); } private byte[] extractSalt(String encPass) { String encPassNoLabel = encPass.substring(6); - byte[] hashAndSalt = Base64.decode(encPassNoLabel.getBytes()); + byte[] hashAndSalt = Base64.getDecoder().decode(encPassNoLabel.getBytes()); int saltLength = hashAndSalt.length - SHA_LENGTH; byte[] salt = new byte[saltLength]; System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength); diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java index 433960349c..240e6b34ea 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java @@ -15,7 +15,8 @@ */ package org.springframework.security.authentication.encoding; -import org.springframework.security.crypto.codec.Base64; +import java.util.Base64; + import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.codec.Utf8; @@ -57,7 +58,7 @@ public class Md4PasswordEncoder extends BaseDigestPasswordEncoder { byte[] resBuf = md4.digest(); if (getEncodeHashAsBase64()) { - return Utf8.decode(Base64.encode(resBuf)); + return Utf8.decode(Base64.getEncoder().encode(resBuf)); } else { return new String(Hex.encode(resBuf)); diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java index f1d8284759..7d977851f8 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ package org.springframework.security.authentication.encoding; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Base64; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.codec.Utf8; import org.springframework.util.Assert; @@ -106,7 +106,7 @@ public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder { } if (getEncodeHashAsBase64()) { - return Utf8.decode(Base64.encode(digest)); + return Utf8.decode(Base64.getEncoder().encode(digest)); } else { return new String(Hex.encode(digest)); diff --git a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java index fd9563e954..b376cc4baf 100644 --- a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java +++ b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package org.springframework.security.core.token; import java.security.SecureRandom; +import java.util.Base64; import java.util.Date; import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.codec.Utf8; import org.springframework.util.Assert; @@ -89,7 +89,7 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi // Compute key String sha512Hex = Sha512DigestUtils.shaHex(content + ":" + serverSecret); String keyPayload = content + ":" + sha512Hex; - String key = Utf8.decode(Base64.encode(Utf8.encode(keyPayload))); + String key = Utf8.decode(Base64.getEncoder().encode(Utf8.encode(keyPayload))); return new DefaultToken(key, creationTime, extendedInformation); } @@ -99,7 +99,7 @@ public class KeyBasedPersistenceTokenService implements TokenService, Initializi return null; } String[] tokens = StringUtils.delimitedListToStringArray( - Utf8.decode(Base64.decode(Utf8.encode(key))), ":"); + Utf8.decode(Base64.getDecoder().decode(Utf8.encode(key))), ":"); Assert.isTrue(tokens.length >= 4, "Expected 4 or more tokens but found " + tokens.length); diff --git a/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java b/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java deleted file mode 100644 index 53a2f1ac88..0000000000 --- a/crypto/src/main/java/org/springframework/security/crypto/codec/Base64.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.crypto.codec; - -/** - * Base64 encoder which is a reduced version of Robert Harder's public domain - * implementation (version 2.3.7). See http://iharder.net/base64 for more information. - *

- * For internal use only. - * - * @author Luke Taylor - * @since 3.0 - */ -public final class Base64 { - - /** No options specified. Value is zero. */ - public final static int NO_OPTIONS = 0; - - /** Specify encoding in first bit. Value is one. */ - public final static int ENCODE = 1; - - /** Specify decoding in first bit. Value is zero. */ - public final static int DECODE = 0; - - /** Do break lines when encoding. Value is 8. */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described in - * Section 4 of RFC3548: http://www.faqs - * .org/rfcs/rfc3548.html. It is important to note that data encoded this way is - * not officially valid Base64, or at the very least should not be called - * Base64 without also specifying that is was encoded using the URL- and Filename-safe - * dialect. - */ - public final static int URL_SAFE = 16; - - /** - * Encode using the special "ordered" dialect of Base64 described here: http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - /** Maximum line length (76) of Base64 output. */ - private final static int MAX_LINE_LENGTH = 76; - - /** The equals sign (=) as a byte. */ - private final static byte EQUALS_SIGN = (byte) '='; - - /** The new line character (\n) as a byte. */ - private final static byte NEW_LINE = (byte) '\n'; - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** The 64 valid Base64 values. */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { (byte) 'A', (byte) 'B', - (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', - (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', - (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', - (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', - (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', - (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', - (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', - (byte) '+', (byte) '/' }; - - /** - * Translates a Base64 value to either its 6-bit reconstruction value or a negative - * number indicating some other meaning. - **/ - private final static byte[] _STANDARD_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, - -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" - * and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { (byte) 'A', (byte) 'B', - (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', - (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', - (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', - (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', - (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', - (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', - (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', - (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', - (byte) '-', (byte) '_' }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, - -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * I don't get the point of this technique, but someone requested it, and it is - * described here: http://www.faqs.org/ - * qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { (byte) '-', (byte) '0', (byte) '1', - (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', - (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', - (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', - (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', - (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', - (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) '_', (byte) 'a', - (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', - (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', - (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', - (byte) 'z' }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { -9, -9, -9, -9, -9, -9, -9, -9, - -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - public static byte[] decode(byte[] bytes) { - return decode(bytes, 0, bytes.length, NO_OPTIONS); - } - - public static byte[] encode(byte[] bytes) { - return encodeBytesToBytes(bytes, 0, bytes.length, NO_OPTIONS); - } - - public static boolean isBase64(byte[] bytes) { - try { - decode(bytes); - } - catch (InvalidBase64CharacterException e) { - return false; - } - return true; - } - - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options - * specified. It's possible, though silly, to specify ORDERED and URLSAFE in - * which case one of them will be picked, though there is no guarantee as to which one - * will be picked. - */ - private static byte[] getAlphabet(int options) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_ALPHABET; - } - else if ((options & ORDERED) == ORDERED) { - return _ORDERED_ALPHABET; - } - else { - return _STANDARD_ALPHABET; - } - } - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options - * specified. It's possible, though silly, to specify ORDERED and URL_SAFE in which - * case one of them will be picked, though there is no guarantee as to which one will - * be picked. - */ - private static byte[] getDecodabet(int options) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } - else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } - else { - return _STANDARD_DECODABET; - } - } - - /* ******** E N C O D I N G M E T H O D S ******** */ - - /** - *

- * Encodes up to three bytes of the array source and writes the resulting - * four Base64 bytes to destination. The source and destination arrays can - * be manipulated anywhere along their length by specifying srcOffset and - * destOffset. This method does not check to make sure your arrays are - * large enough to accomodate srcOffset + 3 for the source array - * or destOffset + 4 for the destination array. The actual - * number of significant bytes in your array is given by numSigBytes. - *

- *

- * This is the lowest level of the encoding methods with all possible parameters. - *

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options) { - - byte[] ALPHABET = getAlphabet(options); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; - return destination; - - case 2: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - case 1: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - default: - return destination; - } - } - - /** - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @see Base64#DO_BREAK_LINES - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 2.3.1 - */ - private static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) { - - if (source == null) { - throw new NullPointerException("Cannot serialize a null array."); - } // end if: null - - if (off < 0) { - throw new IllegalArgumentException("Cannot have negative offset: " + off); - } // end if: off < 0 - - if (len < 0) { - throw new IllegalArgumentException("Cannot have length offset: " + len); - } // end if: len < 0 - - if (off + len > source.length) { - throw new IllegalArgumentException(String.format( - "Cannot have offset of %d and length of %d with array of length %d", - off, len, source.length)); - } // end if: off < 0 - - boolean breakLines = (options & DO_BREAK_LINES) > 0; - - // int len43 = len * 4 / 3; - // byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual - // encoding - if (breakLines) { - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[encLen]; - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - encode3to4(source, d + off, 3, outBuff, e, options); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, options); - e += 4; - } // end if: some padding needed - - // Only resize array if we didn't guess it right. - if (e <= outBuff.length - 1) { - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff, 0, finalOut, 0, e); - // System.err.println("Having to resize array from " + outBuff.length + " to " - // + e ); - return finalOut; - } - else { - // System.err.println("No need to resize array."); - return outBuff; - } - } - - /* ******** D E C O D I N G M E T H O D S ******** */ - - /** - * Decodes four bytes from array source and writes the resulting bytes (up - * to three of them) to destination. The source and destination arrays can - * be manipulated anywhere along their length by specifying srcOffset and - * destOffset. This method does not check to make sure your arrays are - * large enough to accomodate srcOffset + 4 for the source array - * or destOffset + 3 for the destination array. This method - * returns the actual number of bytes that were converted from the Base64 encoding. - *

- * This is the lowest level of the decoding methods with all possible parameters. - *

- * - * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid or there is - * not enough room in the array. - * @since 1.3 - */ - private static int decode4to3(final byte[] source, final int srcOffset, - final byte[] destination, final int destOffset, final int options) { - - // Lots of error checking and exception throwing - if (source == null) { - throw new NullPointerException("Source array was null."); - } // end if - if (destination == null) { - throw new NullPointerException("Destination array was null."); - } // end if - if (srcOffset < 0 || srcOffset + 3 >= source.length) { - throw new IllegalArgumentException( - String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", - source.length, srcOffset)); - } // end if - if (destOffset < 0 || destOffset + 2 >= destination.length) { - throw new IllegalArgumentException( - String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", - destination.length, destOffset)); - } // end if - - byte[] DECODABET = getDecodabet(options); - - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } - - // Example: DkL= - else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) - | ((DECODABET[source[srcOffset + 3]] & 0xFF)); - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - - return 3; - } - } - - /** - * Low-level access to decoding ASCII characters in the form of a byte array. - * Ignores GUNZIP option, if it's set. This is not generally a - * recommended method, although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, if you need more speed - * and reduced memory footprint (and aren't gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws IllegalArgumentException If bogus characters exist in source data - */ - private static byte[] decode(final byte[] source, final int off, final int len, - final int options) { - - // Lots of error checking and exception throwing - if (source == null) { - throw new NullPointerException("Cannot decode null source array."); - } // end if - if (off < 0 || off + len > source.length) { - throw new IllegalArgumentException( - String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", - source.length, off, len)); - } // end if - - if (len == 0) { - return new byte[0]; - } - else if (len < 4) { - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " - + len); - } // end if - - byte[] DECODABET = getDecodabet(options); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[len34]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for (i = off; i < off + len; i++) { // Loop through source - - sbiDecode = DECODABET[source[i] & 0xFF]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if (sbiDecode >= WHITE_SPACE_ENC) { - if (sbiDecode >= EQUALS_SIGN_ENC) { - b4[b4Posn++] = source[i]; // Save non-whitespace - if (b4Posn > 3) { // Time to decode? - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if (source[i] == EQUALS_SIGN) { - break; - } - } - } - } - else { - // There's a bad input character in the Base64 stream. - throw new InvalidBase64CharacterException(String.format( - "Bad Base64 input character decimal %d in array position %d", - ((int) source[i]) & 0xFF, i)); - } - } - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } -} - -class InvalidBase64CharacterException extends IllegalArgumentException { - - InvalidBase64CharacterException(String message) { - super(message); - } -} diff --git a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java index c133886cfd..73e6e7f352 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/scrypt/SCryptPasswordEncoder.java @@ -1,186 +1,187 @@ -/* - * Copyright 2002-2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.crypto.scrypt; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.bouncycastle.crypto.generators.SCrypt; -import org.springframework.security.crypto.codec.Base64; -import org.springframework.security.crypto.codec.Utf8; -import org.springframework.security.crypto.keygen.BytesKeyGenerator; -import org.springframework.security.crypto.keygen.KeyGenerators; -import org.springframework.security.crypto.password.PasswordEncoder; - -/** - *

- * Implementation of PasswordEncoder that uses the SCrypt hashing function. - * Clients can optionally supply a cpu cost parameter, a memory cost parameter - * and a parallelization parameter. - *

- * - *

- * A few - * warnings: - *

- * - * - * - * @author Shazin Sadakath - * @author Rob Winch - * - */ -public class SCryptPasswordEncoder implements PasswordEncoder { - - private final Log logger = LogFactory.getLog(getClass()); - - private final int cpuCost; - - private final int memoryCost; - - private final int parallelization; - - private final int keyLength; - - private final BytesKeyGenerator saltGenerator; - - public SCryptPasswordEncoder() { - this(16384, 8, 1, 32, 64); - } - - /** - * Creates a new instance - * - * @param cpuCost - * cpu cost of the algorithm (as defined in scrypt this is N). - * must be power of 2 greater than 1. Default is currently 16,348 - * or 2^14) - * @param memoryCost - * memory cost of the algorithm (as defined in scrypt this is r) - * Default is currently 8. - * @param parallelization - * the parallelization of the algorithm (as defined in scrypt - * this is p) Default is currently 1. Note that the - * implementation does not currently take advantage of - * parallelization. - * @param keyLength - * key length for the algorithm (as defined in scrypt this is - * dkLen). The default is currently 32. - * @param saltLength - * salt length (as defined in scrypt this is the length of S). - * The default is currently 64. - */ - public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength) { - if (cpuCost <= 1) { - throw new IllegalArgumentException("Cpu cost parameter must be > 1."); - } - if (memoryCost == 1 && cpuCost > 65536) { - throw new IllegalArgumentException("Cpu cost parameter must be > 1 and < 65536."); - } - if (memoryCost < 1) { - throw new IllegalArgumentException("Memory cost must be >= 1."); - } - int maxParallel = Integer.MAX_VALUE / (128 * memoryCost * 8); - if (parallelization < 1 || parallelization > maxParallel) { - throw new IllegalArgumentException("Parallelisation parameter p must be >= 1 and <= " + maxParallel - + " (based on block size r of " + memoryCost + ")"); - } - if (keyLength < 1 || keyLength > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Key length must be >= 1 and <= " + Integer.MAX_VALUE); - } - if (saltLength < 1 || saltLength > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Salt length must be >= 1 and <= " + Integer.MAX_VALUE); - } - - this.cpuCost = cpuCost; - this.memoryCost = memoryCost; - this.parallelization = parallelization; - this.keyLength = keyLength; - this.saltGenerator = KeyGenerators.secureRandom(saltLength); - } - - public String encode(CharSequence rawPassword) { - return digest(rawPassword, saltGenerator.generateKey()); - } - - public boolean matches(CharSequence rawPassword, String encodedPassword) { - if (encodedPassword == null || encodedPassword.length() < keyLength) { - logger.warn("Empty encoded password"); - return false; - } - return decodeAndCheckMatches(rawPassword, encodedPassword); - } - - private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) { - String[] parts = encodedPassword.split("\\$"); - - if (parts.length != 4) { - return false; - } - - long params = Long.parseLong(parts[1], 16); - byte[] salt = decodePart(parts[2]); - byte[] derived = decodePart(parts[3]); - - int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); - int memoryCost = (int) params >> 8 & 0xff; - int parallelization = (int) params & 0xff; - - byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, - keyLength); - - if (derived.length != generated.length) { - return false; - } - - int result = 0; - for (int i = 0; i < derived.length; i++) { - result |= derived[i] ^ generated[i]; - } - return result == 0; - } - - private String digest(CharSequence rawPassword, byte[] salt) { - byte[] derived = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, keyLength); - - String params = Long - .toString(((int) (Math.log(cpuCost) / Math.log(2)) << 16L) | memoryCost << 8 | parallelization, 16); - - StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2); - sb.append("$").append(params).append('$'); - sb.append(encodePart(salt)).append('$'); - sb.append(encodePart(derived)); - - return sb.toString(); - } - - private byte[] decodePart(String part) { - return Base64.decode(Utf8.encode(part)); - } - - private String encodePart(byte[] part) { - return Utf8.decode(Base64.encode(part)); - } -} \ No newline at end of file +/* + * Copyright 2002-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.security.crypto.scrypt; + +import java.util.Base64; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.crypto.generators.SCrypt; +import org.springframework.security.crypto.codec.Utf8; +import org.springframework.security.crypto.keygen.BytesKeyGenerator; +import org.springframework.security.crypto.keygen.KeyGenerators; +import org.springframework.security.crypto.password.PasswordEncoder; + +/** + *

+ * Implementation of PasswordEncoder that uses the SCrypt hashing function. + * Clients can optionally supply a cpu cost parameter, a memory cost parameter + * and a parallelization parameter. + *

+ * + *

+ * A few + * warnings: + *

+ * + * + * + * @author Shazin Sadakath + * @author Rob Winch + * + */ +public class SCryptPasswordEncoder implements PasswordEncoder { + + private final Log logger = LogFactory.getLog(getClass()); + + private final int cpuCost; + + private final int memoryCost; + + private final int parallelization; + + private final int keyLength; + + private final BytesKeyGenerator saltGenerator; + + public SCryptPasswordEncoder() { + this(16384, 8, 1, 32, 64); + } + + /** + * Creates a new instance + * + * @param cpuCost + * cpu cost of the algorithm (as defined in scrypt this is N). + * must be power of 2 greater than 1. Default is currently 16,348 + * or 2^14) + * @param memoryCost + * memory cost of the algorithm (as defined in scrypt this is r) + * Default is currently 8. + * @param parallelization + * the parallelization of the algorithm (as defined in scrypt + * this is p) Default is currently 1. Note that the + * implementation does not currently take advantage of + * parallelization. + * @param keyLength + * key length for the algorithm (as defined in scrypt this is + * dkLen). The default is currently 32. + * @param saltLength + * salt length (as defined in scrypt this is the length of S). + * The default is currently 64. + */ + public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength) { + if (cpuCost <= 1) { + throw new IllegalArgumentException("Cpu cost parameter must be > 1."); + } + if (memoryCost == 1 && cpuCost > 65536) { + throw new IllegalArgumentException("Cpu cost parameter must be > 1 and < 65536."); + } + if (memoryCost < 1) { + throw new IllegalArgumentException("Memory cost must be >= 1."); + } + int maxParallel = Integer.MAX_VALUE / (128 * memoryCost * 8); + if (parallelization < 1 || parallelization > maxParallel) { + throw new IllegalArgumentException("Parallelisation parameter p must be >= 1 and <= " + maxParallel + + " (based on block size r of " + memoryCost + ")"); + } + if (keyLength < 1 || keyLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Key length must be >= 1 and <= " + Integer.MAX_VALUE); + } + if (saltLength < 1 || saltLength > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Salt length must be >= 1 and <= " + Integer.MAX_VALUE); + } + + this.cpuCost = cpuCost; + this.memoryCost = memoryCost; + this.parallelization = parallelization; + this.keyLength = keyLength; + this.saltGenerator = KeyGenerators.secureRandom(saltLength); + } + + public String encode(CharSequence rawPassword) { + return digest(rawPassword, saltGenerator.generateKey()); + } + + public boolean matches(CharSequence rawPassword, String encodedPassword) { + if (encodedPassword == null || encodedPassword.length() < keyLength) { + logger.warn("Empty encoded password"); + return false; + } + return decodeAndCheckMatches(rawPassword, encodedPassword); + } + + private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) { + String[] parts = encodedPassword.split("\\$"); + + if (parts.length != 4) { + return false; + } + + long params = Long.parseLong(parts[1], 16); + byte[] salt = decodePart(parts[2]); + byte[] derived = decodePart(parts[3]); + + int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff); + int memoryCost = (int) params >> 8 & 0xff; + int parallelization = (int) params & 0xff; + + byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, + keyLength); + + if (derived.length != generated.length) { + return false; + } + + int result = 0; + for (int i = 0; i < derived.length; i++) { + result |= derived[i] ^ generated[i]; + } + return result == 0; + } + + private String digest(CharSequence rawPassword, byte[] salt) { + byte[] derived = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization, keyLength); + + String params = Long + .toString(((int) (Math.log(cpuCost) / Math.log(2)) << 16L) | memoryCost << 8 | parallelization, 16); + + StringBuilder sb = new StringBuilder((salt.length + derived.length) * 2); + sb.append("$").append(params).append('$'); + sb.append(encodePart(salt)).append('$'); + sb.append(encodePart(derived)); + + return sb.toString(); + } + + private byte[] decodePart(String part) { + return Base64.getDecoder().decode(Utf8.encode(part)); + } + + private String encodePart(byte[] part) { + return Utf8.decode(Base64.getEncoder().encode(part)); + } +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/codec/Base64Tests.java b/crypto/src/test/java/org/springframework/security/crypto/codec/Base64Tests.java deleted file mode 100644 index c45a50bc21..0000000000 --- a/crypto/src/test/java/org/springframework/security/crypto/codec/Base64Tests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2002-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.security.crypto.codec; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.*; - -/** - * @author Luke Taylor - */ -public class Base64Tests { - - @Test - public void isBase64ReturnsTrueForValidBase64() { - new Base64(); // unused - - assertThat(Base64.isBase64(new byte[] { (byte) 'A', (byte) 'B', (byte) 'C', - (byte) 'D' })).isTrue(); - } - - @Test - public void isBase64ReturnsFalseForInvalidBase64() throws Exception { - // Include invalid '`' character - assertThat(Base64.isBase64(new byte[] { (byte) 'A', (byte) 'B', (byte) 'C', - (byte) '`' })).isFalse(); - } - - @Test(expected = NullPointerException.class) - public void isBase64RejectsNull() { - Base64.isBase64(null); - } - - @Test(expected = IllegalArgumentException.class) - public void isBase64RejectsInvalidLength() { - Base64.isBase64(new byte[] { (byte) 'A' }); - } -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DefaultStateGenerator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DefaultStateGenerator.java index 095ba53bd7..3327f35b00 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DefaultStateGenerator.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/DefaultStateGenerator.java @@ -15,7 +15,8 @@ */ package org.springframework.security.oauth2.client.authentication; -import org.springframework.security.crypto.codec.Base64; +import java.util.Base64; + import org.springframework.security.crypto.keygen.BytesKeyGenerator; import org.springframework.security.crypto.keygen.KeyGenerators; import org.springframework.security.crypto.keygen.StringKeyGenerator; @@ -49,6 +50,6 @@ public class DefaultStateGenerator implements StringKeyGenerator { @Override public String generateKey() { - return new String(Base64.encode(keyGenerator.generateKey())); + return new String(Base64.getEncoder().encode(keyGenerator.generateKey())); } } diff --git a/remoting/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java b/remoting/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java index f392d32946..097f16f987 100644 --- a/remoting/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java +++ b/remoting/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java @@ -18,6 +18,7 @@ package org.springframework.security.remoting.httpinvoker; import java.io.IOException; import java.net.HttpURLConnection; +import java.util.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -25,7 +26,6 @@ import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.core.context.SecurityContextHolder; /** @@ -90,7 +90,7 @@ public class AuthenticationSimpleHttpInvokerRequestExecutor extends && !trustResolver.isAnonymous(auth)) { String base64 = auth.getName() + ":" + auth.getCredentials().toString(); con.setRequestProperty("Authorization", - "Basic " + new String(Base64.encode(base64.getBytes()))); + "Basic " + new String(Base64.getEncoder().encode(base64.getBytes()))); if (logger.isDebugEnabled()) { logger.debug("HttpInvocation now presenting via BASIC authentication SecurityContextHolder-derived: " diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index df2c534347..5c52a56dda 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.List; @@ -44,7 +45,6 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; import org.springframework.security.test.web.support.WebTestUtils; @@ -469,7 +469,7 @@ public final class SecurityMockMvcRequestPostProcessors { String signatureValue = md5Hex(toDigest); String nonceValue = expiryTime + ":" + signatureValue; - return new String(Base64.encode(nonceValue.getBytes())); + return new String(Base64.getEncoder().encode(nonceValue.getBytes())); } private String createAuthorizationHeader(MockHttpServletRequest request) { @@ -895,7 +895,7 @@ public final class SecurityMockMvcRequestPostProcessors { catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } - this.headerValue = "Basic " + new String(Base64.encode(toEncode)); + this.headerValue = "Basic " + new String(Base64.getEncoder().encode(toEncode)); } @Override @@ -907,4 +907,4 @@ public final class SecurityMockMvcRequestPostProcessors { private SecurityMockMvcRequestPostProcessors() { } -} \ No newline at end of file +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index 56283d3990..47d50bfd04 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.web.authentication.rememberme; import java.lang.reflect.Method; +import java.util.Base64; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -33,7 +34,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.core.userdetails.UserDetailsService; @@ -50,7 +50,7 @@ import org.springframework.util.StringUtils; * * @author Luke Taylor * @author Rob Winch - * @author Eddú Meléndez + * @author Edd� Mel�ndez * @since 2.0 */ public abstract class AbstractRememberMeServices implements RememberMeServices, @@ -215,13 +215,16 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, cookieValue = cookieValue + "="; } - if (!Base64.isBase64(cookieValue.getBytes())) { + try { + Base64.getDecoder().decode(cookieValue.getBytes()); + } + catch (IllegalArgumentException e) { throw new InvalidCookieException( "Cookie token was not Base64 encoded; value was '" + cookieValue + "'"); } - String cookieAsPlainText = new String(Base64.decode(cookieValue.getBytes())); + String cookieAsPlainText = new String(Base64.getDecoder().decode(cookieValue.getBytes())); String[] tokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER); @@ -256,7 +259,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, String value = sb.toString(); - sb = new StringBuilder(new String(Base64.encode(value.getBytes()))); + sb = new StringBuilder(new String(Base64.getEncoder().encode(value.getBytes()))); while (sb.charAt(sb.length() - 1) == '=') { sb.deleteCharAt(sb.length() - 1); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java index 77efbbc8e1..fce1917302 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.rememberme; import java.security.SecureRandom; import java.util.Arrays; +import java.util.Base64; import java.util.Date; import javax.servlet.http.HttpServletRequest; @@ -24,7 +25,6 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.util.Assert; @@ -185,13 +185,13 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe protected String generateSeriesData() { byte[] newSeries = new byte[seriesLength]; random.nextBytes(newSeries); - return new String(Base64.encode(newSeries)); + return new String(Base64.getEncoder().encode(newSeries)); } protected String generateTokenData() { byte[] newToken = new byte[tokenLength]; random.nextBytes(newToken); - return new String(Base64.encode(newToken)); + return new String(Base64.getEncoder().encode(newToken)); } private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index 1c7902638d..dbd4fa8f17 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.www; import java.io.IOException; +import java.util.Base64; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -31,7 +32,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.NullRememberMeServices; import org.springframework.security.web.authentication.RememberMeServices; @@ -227,7 +227,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { byte[] base64Token = header.substring(6).getBytes("UTF-8"); byte[] decoded; try { - decoded = Base64.decode(base64Token); + decoded = Base64.getDecoder().decode(base64Token); } catch (IllegalArgumentException e) { throw new BadCredentialsException( diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java index 06d489b8ac..abfc06d74d 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.www; import java.io.IOException; +import java.util.Base64; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -27,7 +28,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Ordered; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.AuthenticationEntryPoint; /** @@ -91,7 +91,7 @@ public class DigestAuthenticationEntryPoint implements AuthenticationEntryPoint, long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000); String signatureValue = DigestAuthUtils.md5Hex(expiryTime + ":" + key); String nonceValue = expiryTime + ":" + signatureValue; - String nonceValueBase64 = new String(Base64.encode(nonceValue.getBytes())); + String nonceValueBase64 = new String(Base64.getEncoder().encode(nonceValue.getBytes())); // qop is quality of protection, as defined by RFC 2617. // we do not use opaque due to IE violation of RFC 2617 in not diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java index 805660b1b3..6def7adb76 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.www; import java.io.IOException; +import java.util.Base64; import java.util.Map; import javax.servlet.FilterChain; @@ -46,7 +47,6 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.cache.NullUserCache; -import org.springframework.security.crypto.codec.Base64; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -390,7 +390,10 @@ public class DigestAuthenticationFilter extends GenericFilterBean } // Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint) - if (!Base64.isBase64(this.nonce.getBytes())) { + try { + Base64.getDecoder().decode(this.nonce.getBytes()); + } + catch (IllegalArgumentException e) { throw new BadCredentialsException(DigestAuthenticationFilter.this.messages .getMessage("DigestAuthenticationFilter.nonceEncoding", new Object[] { this.nonce }, @@ -400,7 +403,7 @@ public class DigestAuthenticationFilter extends GenericFilterBean // Decode nonce from Base64 // format of nonce is: // base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key)) - String nonceAsPlainText = new String(Base64.decode(this.nonce.getBytes())); + String nonceAsPlainText = new String(Base64.getDecoder().decode(this.nonce.getBytes())); String[] nonceTokens = StringUtils .delimitedListToStringArray(nonceAsPlainText, ":");