This is an automated email from the ASF dual-hosted git repository.

bmarwell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shiro.git


The following commit(s) were added to refs/heads/master by this push:
     new 8590adc  SHIRO-349 Security: Byte arrays (and other memory) holding 
sensitive data (even temporarily) should be zerod-out
     new dfda698  Merge pull request #254 from bmhm/SHIRO-349
8590adc is described below

commit 8590adce20ac0942a2bf27716af2058bc2cde574
Author: Eric Cho <[email protected]>
AuthorDate: Thu Aug 20 21:24:01 2020 +0200

    SHIRO-349 Security: Byte arrays (and other memory) holding sensitive data 
(even temporarily) should be zerod-out
---
 .../shiro/mgt/AbstractRememberMeManager.java       | 15 +++--
 .../shiro/crypto/cipher/ByteSourceBroker.java      | 48 ++++++++++++++
 .../shiro/crypto/cipher/ByteSourceUser.java}       | 24 +++----
 .../apache/shiro/crypto/cipher/CipherService.java  |  2 +-
 .../shiro/crypto/cipher/JcaCipherService.java      | 10 ++-
 .../crypto/cipher/SimpleByteSourceBroker.java      | 76 ++++++++++++++++++++++
 .../crypto/cipher/AesCipherServiceTest.groovy      | 63 ++++++++++++++++--
 .../crypto/cipher/BlowfishCipherServiceTest.groovy | 17 ++++-
 .../crypto/cipher/JcaCipherServiceTest.groovy      |  9 ++-
 .../org/apache/shiro/util/ByteSourceWrapper.java   | 59 +++++++++++++++++
 .../main/java/org/apache/shiro/util/ByteUtils.java | 41 ++++++++++++
 11 files changed, 328 insertions(+), 36 deletions(-)

diff --git 
a/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java 
b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
index 72a25dc..9908c2c 100644
--- a/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
+++ b/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java
@@ -22,20 +22,22 @@ import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.authc.RememberMeAuthenticationToken;
+import org.apache.shiro.crypto.cipher.ByteSourceBroker;
 import org.apache.shiro.crypto.cipher.AesCipherService;
 import org.apache.shiro.crypto.cipher.CipherService;
 import org.apache.shiro.lang.io.DefaultSerializer;
 import org.apache.shiro.lang.io.Serializer;
+import org.apache.shiro.lang.util.ByteSource;
 import org.apache.shiro.subject.PrincipalCollection;
 import org.apache.shiro.subject.Subject;
 import org.apache.shiro.subject.SubjectContext;
-import org.apache.shiro.lang.util.ByteSource;
+import org.apache.shiro.util.ByteUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Abstract implementation of the {@code RememberMeManager} interface that 
handles
- * {@link #setSerializer(org.apache.shiro.lang.io.Serializer) serialization} 
and
+ * {@link #setSerializer(Serializer)  serialization} and
  * {@link #setCipherService encryption} of the remembered user identity.
  * <p/>
  * The remembered identity storage location and details are left to subclasses.
@@ -378,14 +380,17 @@ public abstract class AbstractRememberMeManager 
implements RememberMeManager {
      */
     public PrincipalCollection getRememberedPrincipals(SubjectContext 
subjectContext) {
         PrincipalCollection principals = null;
+        byte[] bytes = null;
         try {
-            byte[] bytes = getRememberedSerializedIdentity(subjectContext);
+            bytes = getRememberedSerializedIdentity(subjectContext);
             //SHIRO-138 - only call convertBytesToPrincipals if bytes exist:
             if (bytes != null && bytes.length > 0) {
                 principals = convertBytesToPrincipals(bytes, subjectContext);
             }
         } catch (RuntimeException re) {
             principals = onRememberedPrincipalFailure(re, subjectContext);
+        } finally {
+            ByteUtils.wipe(bytes);
         }
 
         return principals;
@@ -478,8 +483,8 @@ public abstract class AbstractRememberMeManager implements 
RememberMeManager {
         byte[] serialized = encrypted;
         CipherService cipherService = getCipherService();
         if (cipherService != null) {
-            ByteSource byteSource = cipherService.decrypt(encrypted, 
getDecryptionCipherKey());
-            serialized = byteSource.getBytes();
+            ByteSourceBroker broker = cipherService.decrypt(encrypted, 
getDecryptionCipherKey());
+            serialized = broker.getClonedBytes();
         }
         return serialized;
     }
diff --git 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceBroker.java
 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceBroker.java
new file mode 100644
index 0000000..04a7750
--- /dev/null
+++ 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceBroker.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.shiro.crypto.cipher;
+
+/**
+ * ByteSourceBroker holds an encrypted value to decrypt it on demand.
+ * <br/>
+ * {@link #useBytes(ByteSourceUser)} method is designed for dictating
+ * developers to use the byte source in a special way, to prevent its 
prevalence
+ * and difficulty of managing & zeroing that critical information at end of 
use.
+ * <br/>
+ * For exceptional cases we allow developers to use the other method,
+ * {@link #getClonedBytes()}, but it's not advised.
+ */
+public interface ByteSourceBroker {
+    /**
+     * This method accepts an implementation of ByteSourceUser functional 
interface.
+     * <br/>
+     * To limit the decrypted value's existence, developers should maintain the
+     * implementation part as short as possible.
+     *
+     * @param user Implements a use-case for the decrypted value.
+     */
+    void useBytes(ByteSourceUser user);
+
+    /**
+     * As the name implies, this returns a cloned byte array
+     * and caller has a responsibility to wipe it out at end of use.
+     */
+    byte[] getClonedBytes();
+}
diff --git 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceUser.java
similarity index 61%
copy from 
crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
copy to 
crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceUser.java
index d9fd669..2fc7830 100644
--- 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
+++ 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/ByteSourceUser.java
@@ -16,20 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.shiro.crypto.cipher
 
-import org.apache.shiro.crypto.CryptoException
-import org.apache.shiro.crypto.cipher.JcaCipherService
-import org.junit.Test;
-
-public class JcaCipherServiceTest {
-
-    @Test(expected = CryptoException.class)
-    public void testDecrypt() {
-        JcaCipherService cipherService = new JcaCipherService("AES"){};
-        String ciphertext = "iv_helloword";
-        String key = "somekey";
-        cipherService.decrypt(ciphertext.getBytes(), key.getBytes());
-    }
+package org.apache.shiro.crypto.cipher;
 
+/**
+ * {@link ByteSourceBroker#useBytes(ByteSourceUser)} method requires 
ByteSourceUser argument,
+ * and developers should implement how we use the byte arrays in our code-base.
+ * <br/>
+ * The byte array "bytes" could be a decrypted password in plaintext format, 
or other
+ * sensitive information that needs to be erased at end of use.
+ */
+public interface ByteSourceUser {
+    void use(byte[] bytes);
 }
diff --git 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/CipherService.java 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/CipherService.java
index cd3ebf6..67f10fc 100644
--- 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/CipherService.java
+++ 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/CipherService.java
@@ -96,7 +96,7 @@ public interface CipherService {
      * @return a byte source representing the original form of the specified 
encrypted data.
      * @throws CryptoException if there is an error during decryption
      */
-    ByteSource decrypt(byte[] encrypted, byte[] decryptionKey) throws 
CryptoException;
+    ByteSourceBroker decrypt(byte[] encrypted, byte[] decryptionKey) throws 
CryptoException;
 
     /**
      * Receives encrypted data from the given {@code InputStream}, decrypts 
it, and sends the resulting decrypted data
diff --git 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/JcaCipherService.java
 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/JcaCipherService.java
index 61da83d..8f5f4cb 100644
--- 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/JcaCipherService.java
+++ 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/JcaCipherService.java
@@ -345,7 +345,11 @@ public abstract class JcaCipherService implements 
CipherService {
         return ByteSource.Util.bytes(output);
     }
 
-    public ByteSource decrypt(byte[] ciphertext, byte[] key) throws 
CryptoException {
+    public ByteSourceBroker decrypt(byte[] ciphertext, byte[] key) throws 
CryptoException {
+        return new SimpleByteSourceBroker(this, ciphertext, key);
+    }
+
+    ByteSource decryptInternal(byte[] ciphertext, byte[] key) throws 
CryptoException {
 
         byte[] encrypted = ciphertext;
 
@@ -380,10 +384,10 @@ public abstract class JcaCipherService implements 
CipherService {
             }
         }
 
-        return decrypt(encrypted, key, iv);
+        return decryptInternal(encrypted, key, iv);
     }
 
-    private ByteSource decrypt(byte[] ciphertext, byte[] key, byte[] iv) 
throws CryptoException {
+    private ByteSource decryptInternal(byte[] ciphertext, byte[] key, byte[] 
iv) throws CryptoException {
         if (log.isTraceEnabled()) {
             log.trace("Attempting to decrypt incoming byte array of length " +
                     (ciphertext != null ? ciphertext.length : 0));
diff --git 
a/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/SimpleByteSourceBroker.java
 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/SimpleByteSourceBroker.java
new file mode 100644
index 0000000..c56e627
--- /dev/null
+++ 
b/crypto/cipher/src/main/java/org/apache/shiro/crypto/cipher/SimpleByteSourceBroker.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.shiro.crypto.cipher;
+
+import org.apache.shiro.lang.util.ByteSource;
+import org.apache.shiro.lang.util.Destroyable;
+import org.apache.shiro.util.ByteSourceWrapper;
+import org.apache.shiro.util.ByteUtils;
+
+import java.io.IOException;
+
+/**
+ * A simple implementation that maintains cipher service, ciphertext and key 
for decrypting it later.
+ * {@link #useBytes(ByteSourceUser)} guarantees the sensitive data in byte 
array will be erased at end of use.
+ */
+public class SimpleByteSourceBroker implements ByteSourceBroker, Destroyable {
+    private JcaCipherService cipherService;
+    private byte[] ciphertext;
+    private byte[] key;
+    private boolean destroyed = false;
+
+    public SimpleByteSourceBroker(JcaCipherService cipherService, byte[] 
ciphertext, byte[] key) {
+        this.cipherService = cipherService;
+        this.ciphertext = ciphertext.clone();
+        this.key = key.clone();
+    }
+
+    public synchronized void useBytes(ByteSourceUser user) {
+        if (destroyed || user == null) {
+            return;
+        }
+        ByteSource byteSource = cipherService.decryptInternal(ciphertext, key);
+
+        try (ByteSourceWrapper temp = 
ByteSourceWrapper.wrap(byteSource.getBytes())) {
+            user.use(temp.getBytes());
+        } catch (IOException e) {
+            // ignore
+        }
+
+    }
+
+    public byte[] getClonedBytes() {
+        ByteSource byteSource = cipherService.decryptInternal(ciphertext, key);
+        return byteSource.getBytes(); // this's a newly created byte array
+    }
+
+    public void destroy() throws Exception {
+        if (!destroyed) {
+            synchronized (this) {
+                destroyed = true;
+                cipherService = null;
+                ByteUtils.wipe(ciphertext);
+                ciphertext = null;
+                ByteUtils.wipe(key);
+                key = null;
+            }
+        }
+    }
+}
diff --git 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/AesCipherServiceTest.groovy
 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/AesCipherServiceTest.groovy
index 2ab0d43..bc9a00a 100644
--- 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/AesCipherServiceTest.groovy
+++ 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/AesCipherServiceTest.groovy
@@ -19,15 +19,16 @@
 package org.apache.shiro.crypto.cipher
 
 
+import org.apache.shiro.lang.codec.CodecSupport
+import org.apache.shiro.lang.util.ByteSource
+import org.apache.shiro.lang.util.Destroyable
+import org.apache.shiro.util.ByteUtils
 import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.junit.Test
 
 import java.security.Security
 
-import static org.junit.Assert.*;
-
-import org.apache.shiro.lang.codec.CodecSupport
-import org.apache.shiro.lang.util.ByteSource
-import org.junit.Test
+import static org.junit.Assert.assertTrue
 
 /**
  * Test class for the AesCipherService class.
@@ -52,12 +53,60 @@ class AesCipherServiceTest {
     }
 
     @Test
+    void testBlockOperations_ByteSource() {
+        AesCipherService aes = new AesCipherService();
+
+        byte[] key = aes.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            ByteSource ciphertext = aes.encrypt(plaintext, key);
+            ByteSourceBroker broker = aes.decrypt(ciphertext.getBytes(), key);
+            broker.useBytes(new ByteSourceUser() {
+                @Override
+                void use(byte[] bytes) {
+                    assertTrue(Arrays.equals(plaintext, bytes));
+                }
+            });
+            if (broker instanceof Destroyable) {
+                ((Destroyable) broker).destroy();
+            }
+        }
+    }
+
+    @Test
     void testStreamingOperations() {
         AesCipherService cipher = new AesCipherService()
         assertStreaming(cipher)
     }
 
     @Test
+    void testStreamingOperations_ByteSource() {
+
+        AesCipherService cipher = new AesCipherService();
+        byte[] key = cipher.generateNewKey().getEncoded();
+
+        for (String plain : PLAINTEXTS) {
+            byte[] plaintext = CodecSupport.toBytes(plain);
+            InputStream plainIn = new ByteArrayInputStream(plaintext);
+            ByteArrayOutputStream cipherOut = new ByteArrayOutputStream();
+            cipher.encrypt(plainIn, cipherOut, key);
+
+            byte[] ciphertext = cipherOut.toByteArray();
+            InputStream cipherIn = new ByteArrayInputStream(ciphertext);
+            ByteArrayOutputStream plainOut = new ByteArrayOutputStream();
+            cipher.decrypt(cipherIn, plainOut, key);
+
+            byte[] decrypted = plainOut.toByteArray();
+            try {
+                assertTrue(Arrays.equals(plaintext, decrypted));
+            } finally {
+                ByteUtils.wipe(decrypted);
+            }
+        }
+    }
+
+    @Test
     void testAesGcm() {
         assertBlock(OperationMode.GCM)
         assertStreaming(OperationMode.GCM)
@@ -155,8 +204,8 @@ class AesCipherServiceTest {
         for (String plain : PLAINTEXTS) {
             byte[] plaintext = CodecSupport.toBytes(plain)
             ByteSource ciphertext = cipher.encrypt(plaintext, key)
-            ByteSource decrypted = cipher.decrypt(ciphertext.getBytes(), key)
-            assertTrue(Arrays.equals(plaintext, decrypted.getBytes()))
+            ByteSourceBroker decrypted = cipher.decrypt(ciphertext.getBytes(), 
key)
+            assertTrue(Arrays.equals(plaintext, decrypted.getClonedBytes()))
         }
     }
 
diff --git 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/BlowfishCipherServiceTest.groovy
 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/BlowfishCipherServiceTest.groovy
index d3f2c3a..9c86b21 100644
--- 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/BlowfishCipherServiceTest.groovy
+++ 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/BlowfishCipherServiceTest.groovy
@@ -18,8 +18,10 @@
  */
 package org.apache.shiro.crypto.cipher
 
+
 import org.apache.shiro.lang.codec.CodecSupport
 import org.apache.shiro.lang.util.ByteSource
+import org.apache.shiro.util.ByteUtils
 import org.junit.Test
 
 import static org.junit.Assert.assertTrue
@@ -45,8 +47,13 @@ public class BlowfishCipherServiceTest {
         for (String plain : PLAINTEXTS) {
             byte[] plaintext = CodecSupport.toBytes(plain);
             ByteSource ciphertext = blowfish.encrypt(plaintext, key);
-            ByteSource decrypted = blowfish.decrypt(ciphertext.getBytes(), 
key);
-            assertTrue(Arrays.equals(plaintext, decrypted.getBytes()));
+            ByteSourceBroker broker = blowfish.decrypt(ciphertext.getBytes(), 
key);
+            byte[] decrypted = broker.getClonedBytes()
+            try {
+                assertTrue(Arrays.equals(plaintext, decrypted));
+            } finally {
+                ByteUtils.wipe(decrypted);
+            }
         }
     }
 
@@ -68,7 +75,11 @@ public class BlowfishCipherServiceTest {
             cipher.decrypt(cipherIn, plainOut, key);
 
             byte[] decrypted = plainOut.toByteArray();
-            assertTrue(Arrays.equals(plaintext, decrypted));
+            try {
+                assertTrue(Arrays.equals(plaintext, decrypted));
+            } finally {
+                ByteUtils.wipe(decrypted);
+            }
         }
 
     }
diff --git 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
index d9fd669..9cc6fe2 100644
--- 
a/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
+++ 
b/crypto/cipher/src/test/groovy/org/apache/shiro/crypto/cipher/JcaCipherServiceTest.groovy
@@ -19,8 +19,7 @@
 package org.apache.shiro.crypto.cipher
 
 import org.apache.shiro.crypto.CryptoException
-import org.apache.shiro.crypto.cipher.JcaCipherService
-import org.junit.Test;
+import org.junit.Test
 
 public class JcaCipherServiceTest {
 
@@ -29,7 +28,11 @@ public class JcaCipherServiceTest {
         JcaCipherService cipherService = new JcaCipherService("AES"){};
         String ciphertext = "iv_helloword";
         String key = "somekey";
-        cipherService.decrypt(ciphertext.getBytes(), key.getBytes());
+        def broker = cipherService.decrypt(ciphertext.getBytes(), 
key.getBytes());
+        // throws exception.
+        broker.useBytes { byte[] bytes ->
+            // noop
+        };
     }
 
 }
diff --git a/lang/src/main/java/org/apache/shiro/util/ByteSourceWrapper.java 
b/lang/src/main/java/org/apache/shiro/util/ByteSourceWrapper.java
new file mode 100644
index 0000000..6839668
--- /dev/null
+++ b/lang/src/main/java/org/apache/shiro/util/ByteSourceWrapper.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.shiro.util;
+
+import org.apache.shiro.lang.util.ByteSource;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * To use try-with-resources idiom, this class supports wrapping existing 
ByteSource
+ * object or byte array. At end of try block, it gets zeroed out automatically.
+ */
+public class ByteSourceWrapper implements Closeable {
+    private byte[] bytes;
+
+    private ByteSourceWrapper(byte[] bytes) {
+        this.bytes = bytes;
+    }
+
+    /**
+     * This method generically accepts byte array or ByteSource instance.
+     */
+    public static ByteSourceWrapper wrap(Object value) {
+        if (value instanceof byte[]) {
+            byte[] bytes = (byte[]) value;
+            return new ByteSourceWrapper(bytes);
+        } else if (value instanceof ByteSource) {
+            byte[] bytes = ((ByteSource) value).getBytes();
+            return new ByteSourceWrapper(bytes);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    public byte[] getBytes() {
+        return bytes;
+    }
+
+    public void close() throws IOException {
+        ByteUtils.wipe(bytes);
+    }
+}
diff --git a/lang/src/main/java/org/apache/shiro/util/ByteUtils.java 
b/lang/src/main/java/org/apache/shiro/util/ByteUtils.java
new file mode 100644
index 0000000..39caed4
--- /dev/null
+++ b/lang/src/main/java/org/apache/shiro/util/ByteUtils.java
@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2018 The shiro-root contributors
+ *
+ *  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.apache.shiro.util;
+
+import java.util.Arrays;
+
+public final class ByteUtils {
+
+  private ByteUtils() {
+    // private utility class
+  }
+
+  /**
+   * For security, sensitive information in array should be zeroed-out at end 
of use (SHIRO-349).
+   * @param value An array holding sensitive data
+   */
+  public static void wipe(Object value) {
+    if (value instanceof byte[]) {
+      byte[] array = (byte[]) value;
+      Arrays.fill(array, (byte) 0);
+    } else if (value instanceof char[]) {
+      char[] array = (char[]) value;
+      Arrays.fill(array, '\u0000');
+    }
+  }
+
+}

Reply via email to