From a9fed3f08457bff64ac0e532ff67a1ed3948b86b Mon Sep 17 00:00:00 2001 From: David Engel Date: Thu, 30 Jun 2022 11:35:21 -0700 Subject: [PATCH 1/2] Initialization vector cannot be reused --- .../sqlserver/jdbc/SecureStringUtil.java | 19 +++++++++++++------ .../microsoft/sqlserver/jdbc/UtilTest.java | 10 ++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java index 0b5de8d87..1a11f1520 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java @@ -31,9 +31,6 @@ final class SecureStringUtil { /* authentication tag length in bits */ static final int TAG_LENGTH = 16; - /* initialization vector */ - byte[] iv; - /** secret key for encryption/decryption */ SecretKeySpec secretKey; @@ -68,7 +65,6 @@ static SecureStringUtil getInstance() throws SQLServerException { * if error */ private SecureStringUtil() throws SQLServerException { - iv = new byte[IV_LENGTH]; try { // generate key */ KeyGenerator keygen = KeyGenerator.getInstance(KEYGEN_ALGORITHEM); @@ -99,6 +95,8 @@ private SecureStringUtil() throws SQLServerException { byte[] getEncryptedBytes(char[] chars) throws SQLServerException { if (chars == null) return null; + + byte[] iv = new byte[IV_LENGTH]; SecureRandom random = new SecureRandom(); random.nextBytes(iv); GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv); @@ -107,7 +105,10 @@ byte[] getEncryptedBytes(char[] chars) throws SQLServerException { encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParamSpec); byte[] cipherText = encryptCipher.doFinal(Util.charsToBytes(chars)); - return cipherText; + byte[] bytes = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, bytes, 0, iv.length); + System.arraycopy(cipherText, 0, bytes, iv.length, cipherText.length); + return bytes; } catch (Exception e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed")); Object[] msgArgs = {e.getMessage()}; @@ -127,13 +128,19 @@ byte[] getEncryptedBytes(char[] chars) throws SQLServerException { char[] getDecryptedChars(byte[] bytes) throws SQLServerException { if (bytes == null) return null; + + byte[] iv = new byte[IV_LENGTH]; + byte[] encryptedBytes = new byte[bytes.length - IV_LENGTH]; + System.arraycopy(bytes, 0, iv, 0, IV_LENGTH); + System.arraycopy(bytes, IV_LENGTH, encryptedBytes, 0, bytes.length - IV_LENGTH); + GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv); byte[] plainText = null; try { decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec); - plainText = decryptCipher.doFinal(bytes); + plainText = decryptCipher.doFinal(encryptedBytes); return Util.bytesToChars(plainText); } catch (Exception e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed")); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java index bddd9a26a..835bab8ef 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java @@ -66,6 +66,8 @@ public void testparseUrl() throws SQLException { } private static String testString = "A รŸ โ‚ฌ ๅ—จ ๐„ž ๐Ÿ™‚ฤƒัฃ๐” ีฎแปลฟฤฃศŸแŽฅ๐’‹วฉฤพแธฟ๊ž‘ศฏ๐˜ฑ๐‘ž๐—‹๐˜ดศถ๐ž„๐œˆฯˆ๐’™๐˜†๐šฃ1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~๐˜ˆแธ†๐–ข๐•ฏูคแธžิะว๐™…ฦ˜ิธโฒ˜๐™‰เงฆฮก๐—คษŒ๐“ขศšะฆ๐’ฑั ๐“งฦณศคังแ–ฏฤ‡๐—ฑแป…๐‘“๐™œแ‚น๐žฒ๐‘—๐’Œฤผแนƒล‰ะพ๐žŽ๐’’แตฒ๊œฑ๐™ฉแปซ๐—ลต๐’™๐’šลบ1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~ะแธ‚โฒค๐——๐–ค๐—™๊ž ๊“งศŠ๐‰๐œฅ๊“ก๐‘€๐‘ตวฌ๐™ฟ๐‘„ล–๐‘†๐’ฏ๐–ด๐˜๐˜ž๊“ซลธ๐œกแบฃ๐˜ขฦ€๐–ผแธ‹แบฟแตฎโ„Š๐™แŽฅ๐•›ะบฮนแนƒีคโฑบ๐“…๐˜ฒ๐•ฃ๐–˜ลง๐‘ขแนฝแบ‰๐˜…แƒงลพ1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~ัฆ๐™ฑฦ‡แ—žฮฃโ„ฑิาคูก๐”ะš๐“›๐“œฦศŽ๐šธ๐‘„แนš๐“ขแนฎแนบฦฒแ”๊“ซ๐šˆ๐šญ๐œถแรงแƒซ๐‘’๐–ฟ๐—€แธง๐—‚๐ฃาษญแธฟ๐•Ÿ๐จ๐”๐•ขแน›๐“ผั‚รบ๐”ณแบƒโคฌ๐ฒ๐—“1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~๐– ฮ’๐’ž๐˜‹๐™ด๐“•ฤขศžแปˆ๐•ต๊“—สŸ๐™ผโ„•เงฆ๐šธ๐—คี€๊“ขแนฐว“โ…ค๐”šโฒฌ๐‘Œ๐™•๐˜ข๐•ค"; + private static String testString2 = "ssdfsdflkjh9u0345)*&)(*&%$"; + private static String testString3 = "ss345(*&^%oujdf.';lk2345(*&()*$#~!`1\\]wer><.,/?dfsdflkjh9u0345)*&)(*&%$"; @Test public void testArrayConversions() { @@ -79,9 +81,17 @@ public void testArrayConversions() { @Test public void testSecureStringUtil() throws SQLException { + // Encrypt/decrypt multiple values in overlapping orders byte[] bytes = SecureStringUtil.getInstance().getEncryptedBytes(testString.toCharArray()); + byte[] bytes2 = SecureStringUtil.getInstance().getEncryptedBytes(testString2.toCharArray()); String end = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes)); + byte[] bytes3 = SecureStringUtil.getInstance().getEncryptedBytes(testString3.toCharArray()); + String end3 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes3)); + String end2 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes2)); + assertEquals(testString, end); + assertEquals(testString2, end2); + assertEquals(testString3, end3); } private void writeAndReadLong(long valueToTest) { From 2b6152af6cba2a3566fe37c17fd6ad52b4213ace Mon Sep 17 00:00:00 2001 From: David Engel Date: Mon, 11 Jul 2022 11:06:25 -0700 Subject: [PATCH 2/2] Review feedback / optimization --- .../java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java index 1a11f1520..2ed97efbf 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java @@ -130,9 +130,7 @@ char[] getDecryptedChars(byte[] bytes) throws SQLServerException { return null; byte[] iv = new byte[IV_LENGTH]; - byte[] encryptedBytes = new byte[bytes.length - IV_LENGTH]; System.arraycopy(bytes, 0, iv, 0, IV_LENGTH); - System.arraycopy(bytes, IV_LENGTH, encryptedBytes, 0, bytes.length - IV_LENGTH); GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv); @@ -140,7 +138,7 @@ char[] getDecryptedChars(byte[] bytes) throws SQLServerException { try { decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec); - plainText = decryptCipher.doFinal(encryptedBytes); + plainText = decryptCipher.doFinal(bytes, IV_LENGTH, bytes.length - IV_LENGTH); return Util.bytesToChars(plainText); } catch (Exception e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));