OSDN Git Service

Correct Encryption padding problem.
authorRandy Baumgarte <randy@fbn.cx>
Fri, 7 Oct 2011 16:15:20 +0000 (12:15 -0400)
committerRandy Baumgarte <randy@fbn.cx>
Fri, 7 Oct 2011 16:15:20 +0000 (12:15 -0400)
src/cx/fbn/nevernote/evernote/EnCrypt.java

index e1bbd83..19ce876 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
- * This file is part of NixNote \r
+ * This file is part of NeverNote \r
  * Copyright 2009 Randy Baumgarte\r
  * \r
  * This file may be licensed under the terms of of the\r
 */\r
 package cx.fbn.nevernote.evernote;\r
 \r
-//**********************************************\r
-//**********************************************\r
-//* Utility used to encript or decrypt the \r
-//* text in a note.\r
-//**********************************************\r
-//**********************************************\r
-\r
 import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.CharBuffer;\r
+import java.nio.charset.Charset;\r
 import java.security.InvalidAlgorithmParameterException;\r
 import java.security.InvalidKeyException;\r
 import java.security.MessageDigest;\r
 import java.security.NoSuchAlgorithmException;\r
+import java.util.Arrays;\r
 import java.util.zip.CRC32;\r
 \r
 import javax.crypto.BadPaddingException;\r
@@ -57,32 +54,175 @@ public class EnCrypt {
 \r
       return strbuf.toString();\r
      }\r
+\r
+//     public static class EnCryptException extends Exception {\r
+//      public EnCryptException(String message, Throwable cause) {\r
+//              super(message, cause);\r
+//      }\r
+//     }\r
+     \r
+     /**\r
+      * Choose the character set to use for encoding\r
+      */\r
+     private Charset getCharset() {\r
+        \r
+        // Just hard-coding choice here\r
+       boolean useUtf8 = true; \r
+               \r
+               final Charset charSet;\r
+               if(useUtf8) {\r
+                       charSet = Charset.forName("UTF-8");\r
+               } else {\r
+                       charSet = Charset.defaultCharset();\r
+               }\r
+               return charSet;\r
+     }\r
+     \r
+     \r
+     // Useful for debugging, but not normally used\r
+     @SuppressWarnings("unused")\r
+       private byte[] encodeStringOld(String text) {\r
+\r
+        int len = text.length()+4;\r
+        int mod = (len%8);\r
+        if (mod>0) {\r
+                for (; mod !=0; len++) {\r
+                        mod = len%8;\r
+                }\r
+                len--;\r
+        }\r
+        len = len-4;\r
+        StringBuffer textBuffer = new StringBuffer(text);\r
+        textBuffer.setLength(len);\r
+\r
+        // Setup parms for the cipher\r
+        String encoded = crcHeader(textBuffer.toString()) +textBuffer;\r
+\r
+        return encoded.getBytes();\r
+     }\r
+\r
+     /**\r
+      * Main changes are\r
+      * \r
+      * 1. Do padding based on encoded bytes, not string length (some chars -> 2 bytes)\r
+      * 2. Use specific named charset\r
+      */\r
+     private byte[] encodeStringNew(String text) {\r
+        \r
+        final Charset charSet = getCharset();\r
+               \r
+               // Convert to bytes using given encoding, and align *bytes* to multiple of\r
+               // 8, with 4 bytes reserved for the crc\r
+               final byte[] bytes = text.getBytes(charSet);\r
+               int align8 = (bytes.length + 4) % 8;\r
+               int paddingNeeded = 8 - align8;\r
+               final byte[] paddedBytes = Arrays.copyOf(bytes, bytes.length + paddingNeeded);\r
+               \r
+               // Now calculate the crc, using the bytes\r
+               String crc = crcHeader(paddedBytes);\r
+               \r
+               byte[] crcBytes = crc.getBytes(charSet);\r
+               if(crcBytes.length != 4) {\r
+                       System.err.println("CRC Bytes really should be 4 in length!");\r
+                       return null;\r
+               }\r
+               \r
+               // Now combine crc bytes and string bytes into byte array\r
+               // for encryption\r
+               byte[] total = new byte[paddedBytes.length + crcBytes.length];\r
+               System.arraycopy(crcBytes, 0, total, 0, crcBytes.length);\r
+               System.arraycopy(paddedBytes, 0, total, crcBytes.length, paddedBytes.length);\r
+               \r
+               return total;\r
+     }\r
+\r
+     \r
+     /**\r
+      * Same as for encryption: use named charset, and\r
+      * @param bytes\r
+      * @return\r
+      */\r
+     private String decodeBytesNew(byte[] bytes) {\r
+        \r
+        Charset charSet = getCharset();\r
+        \r
+        byte[] crcBytes = Arrays.copyOfRange(bytes, 0, 4);\r
+        byte[] textBytes = Arrays.copyOfRange(bytes, 4, bytes.length);\r
+        \r
+        CharBuffer crcChar = charSet.decode(ByteBuffer.wrap(crcBytes));\r
+        CharBuffer textChar = charSet.decode(ByteBuffer.wrap(textBytes));\r
+        \r
+        // Get crc of text to see if same\r
+        String cryptCRC = crcChar.toString();\r
+        String realCRC = crcHeader(textBytes);\r
+        \r
+        if(realCRC.equals(cryptCRC)) {\r
+                // Trim nulls at end\r
+                while(textChar.get(textChar.limit() - 1) == 0 && textChar.limit() != 0) {\r
+                        textChar.limit(textChar.limit() - 1);\r
+                }\r
+                String str = textChar.toString();\r
+                return str;\r
+        }\r
+        \r
+        return null;\r
+     }\r
+     \r
+     /**  \r
+      * For reference: old version.  Useful for debugging\r
+      * @param bytes\r
+      * @return\r
+      */  \r
+     @SuppressWarnings("unused")\r
+       private String decodeBytesOld(byte[] bytes) {\r
+\r
+        // We have a result.  Separate it into the 4 byte header and the decrypted text\r
+        StringBuffer buffer = new StringBuffer(new String(bytes));\r
+        String cryptCRC = buffer.substring(0,4);\r
+        String clearText = buffer.substring(4);\r
+        String realCRC = crcHeader(clearText);\r
+        // We need to get the real CRC of the decrypted text\r
+        if (realCRC.equalsIgnoreCase(cryptCRC)) {\r
+                int endPos = clearText.length();\r
+                for (int i=buffer.length()-1; i>=0; i--) {\r
+                        if (buffer.charAt(i) == 0) \r
+                                endPos--;\r
+                        else\r
+                                i=-1;\r
+                }\r
+                clearText = clearText.substring(0,endPos);\r
+                return clearText;\r
+        }\r
+\r
+        return null;\r
+\r
+     }\r
+\r
+     \r
+     \r
        // Encrypte the text and return the base64 string\r
        public String encrypt(String text, String passphrase, int keylen) {\r
+               \r
+               \r
                RC2ParameterSpec parm = new RC2ParameterSpec(keylen);\r
-           MessageDigest md;\r
+          \r
                try {\r
-                       int len = text.length()+4;\r
-                       int mod = (len%8);\r
-                       if (mod>0) {\r
-                               for (; mod !=0; len++) {\r
-                                       mod = len%8;\r
-                               }\r
-                               len--;\r
-                       }\r
-                       len = len-4;\r
-                       StringBuffer textBuffer = new StringBuffer(text);\r
-                       textBuffer.setLength(len);\r
                        // Get a MD5 for the passphrase\r
-                       md = MessageDigest.getInstance("MD5");\r
-                   md.update(passphrase.getBytes());\r
+                        MessageDigest md = MessageDigest.getInstance("MD5");\r
+                        // NB Use specific Charset\r
+                   md.update(passphrase.getBytes(getCharset()));\r
                    \r
                    // Setup parms for the cipher\r
                    SecretKeySpec skeySpec = new SecretKeySpec(md.digest(), "RC2");\r
                        Cipher cipher = Cipher.getInstance("RC2/ECB/NoPadding");\r
                        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, parm);\r
-                       String encoded = crcHeader(textBuffer.toString()) +textBuffer;\r
-                       byte[] d = cipher.doFinal(encoded.getBytes());\r
+                       \r
+                       //byte[] oldBytes = encodeStringOld(text);\r
+                       byte[] newBytes = encodeStringNew(text);\r
+                       //boolean areSame = Arrays.equals(oldBytes, newBytes);\r
+                       //System.out.println("Same? " + areSame);\r
+                       byte[] d = cipher.doFinal(newBytes);\r
+                       \r
                        return Base64.encodeBytes(d);\r
                } catch (NoSuchAlgorithmException e) {\r
                        e.printStackTrace();\r
@@ -107,8 +247,7 @@ public class EnCrypt {
                try {\r
                        // Get a MD5 for the passphrase\r
                        md = MessageDigest.getInstance("MD5");\r
-                       StringBuffer p = new StringBuffer(passphrase);\r
-                       md.update(p.toString().getBytes());\r
+                       md.update(passphrase.getBytes(getCharset()));\r
                    \r
                    // Setup parms for the cipher\r
                    SecretKeySpec skeySpec = new SecretKeySpec(md.digest(), "RC2");\r
@@ -119,23 +258,12 @@ public class EnCrypt {
                        byte[] dString = Base64.decode(text);\r
                        byte[] d = cipher.doFinal(dString);\r
                        \r
-                       // We have a result.  Separate it into the 4 byte header and the decrypted text\r
-                       StringBuffer buffer = new StringBuffer(new String(d));\r
-                       String cryptCRC = buffer.substring(0,4);\r
-                       String clearText = buffer.substring(4);\r
-                       String realCRC = crcHeader(clearText);\r
-                       // We need to get the real CRC of the decrypted text\r
-                       if (realCRC.equalsIgnoreCase(cryptCRC)) {\r
-                               int endPos = clearText.length();\r
-                               for (int i=buffer.length()-1; i>=0; i--) {\r
-                                       if (buffer.charAt(i) == 0) \r
-                                               endPos--;\r
-                                       else\r
-                                               i=-1;\r
-                               }\r
-                               clearText = clearText.substring(0,endPos);\r
-                               return clearText;\r
-                       }\r
+                       //String clearTextOld = decodeBytesOld(d);\r
+                       String clearTextNew = decodeBytesNew(d);\r
+                       //if(clearTextNew != null) {\r
+                       //      System.out.println("Are same decrypted ? " + clearTextNew.equals(clearTextOld));\r
+                       //}\r
+                       return clearTextNew;\r
                } catch (NoSuchAlgorithmException e) {\r
                        e.printStackTrace();\r
                } catch (NoSuchPaddingException e) {\r
@@ -157,8 +285,11 @@ public class EnCrypt {
        // Utility function to return the CRC header of an encoded string.  This is\r
        // used to verify good decryption and put in front of a new encrypted string\r
        private String crcHeader(String text) {\r
+               return crcHeader(text.getBytes());\r
+       }\r
+       private String crcHeader(byte[] bytes) {\r
                CRC32 crc = new CRC32();\r
-               crc.update(text.getBytes());\r
+               crc.update(bytes);\r
                int realCRC = (int)crc.getValue();\r
                \r
                // The first 4 chars of the hex string will equal the first\r
@@ -171,4 +302,4 @@ public class EnCrypt {
 \r
        }\r
 \r
-}\r
+}
\ No newline at end of file