web-dev-qa-db-ja.com

バイト配列を文字列に変換し、バイト配列に戻す問題

このトピックには多くの質問がありますが、同じ解決策ですが、これはうまくいきません。暗号化を使用した簡単なテストがあります。暗号化/復号化自体は機能します(このテストを文字列としてではなく、バイト配列自体で処理する限り)。問題は、それをバイト配列としてではなくストリングとして処理したいのですが、バイト配列をストリングにエンコードしてから戻すと、結果のバイト配列が元のバイト配列と異なるため、復号化が機能しなくなります。対応する文字列メソッドで次のパラメーターを試しました:UTF-8、UTF8、UTF-16、UTF8。それらのどれも動作しません。結果のバイト配列は元の配列とは異なります。これがなぜそうなのでしょうか?

暗号化者:

public class NewEncrypter
{
    private String algorithm = "DESede";
    private Key key = null;
    private Cipher cipher = null;

    public NewEncrypter() throws NoSuchAlgorithmException, NoSuchPaddingException
    {
         key = KeyGenerator.getInstance(algorithm).generateKey();
         cipher = Cipher.getInstance(algorithm);
    }

    public byte[] encrypt(String input) throws Exception
    {
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] inputBytes = input.getBytes("UTF-16");

        return cipher.doFinal(inputBytes);
    }

    public String decrypt(byte[] encryptionBytes) throws Exception
    {
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] recoveredBytes = cipher.doFinal(encryptionBytes);
        String recovered = new String(recoveredBytes, "UTF-16");

        return recovered;
    }
}

これは私がそれを試すテストです:

public class NewEncrypterTest
{
    @Test
    public void canEncryptAndDecrypt() throws Exception
    {
        String toEncrypt = "FOOBAR";

        NewEncrypter encrypter = new NewEncrypter();

        byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
        System.out.println("encryptedByteArray:" + encryptedByteArray);

        String decoded = new String(encryptedByteArray, "UTF-16");
        System.out.println("decoded:" + decoded);

        byte[] encoded = decoded.getBytes("UTF-16");
        System.out.println("encoded:" + encoded);

        String decryptedText = encrypter.decrypt(encoded); //Exception here
        System.out.println("decryptedText:" + decryptedText);

        assertEquals(toEncrypt, decryptedText);
    }
}
49
Bevor

暗号化されたデータは、任意のバイナリデータではなく、人間が読み取れるテキスト用であるため、文字列に保存することはお勧めできません。バイナリデータの場合は、byte[]

ただし、mustを行う場合は、バイトと文字の間に1-to-1マッピングを持つエンコードを使用する必要があります、つまり、すべてのバイトシーケンスを一意の文字シーケンスにマッピングしたり、戻したりすることができます。そのようなエンコーディングの1つはISO-8859-1です。つまり:

    String decoded = new String(encryptedByteArray, "ISO-8859-1");
    System.out.println("decoded:" + decoded);

    byte[] encoded = decoded.getBytes("ISO-8859-1"); 
    System.out.println("encoded:" + Java.util.Arrays.toString(encoded));

    String decryptedText = encrypter.decrypt(encoded);

データを失わない他の一般的なエンコーディングはhexadecimalbase64ですが、残念ながらそれらのヘルパーライブラリが必要です。標準APIはそれらのクラスを定義しません。

UTF-16では、プログラムは次の2つの理由で失敗します。

  1. String.getBytes( "UTF-16")は、バイトオーダーマーカー文字を出力に追加して、バイトの順序を識別します。これが発生しないようにするには、UTF-16LEまたはUTF-16BEを使用する必要があります。
  2. すべてのバイトシーケンスがUTF-16の文字にマップできるわけではありません。まず、UTF-16でエンコードされたテキストには、偶数バイトが必要です。第二に、UTF-16にはU + FFFFを超えるUnicode文字をエンコードするメカニズムがあります。これは、たとえば1つのUnicode文字のみにマップされる4バイトのシーケンスがあります。これを可能にするために、4つの最初の2バイトはUTF-16で文字をエンコードしません。
96
Joni

Stringš, ž, ć, Ō, ō, Ūなどの一般的でない文字が含まれている場合、承認されたソリューションは機能しません。

次のコードは私にとってはうまくいきました。

byte[] myBytes = Something.getMyBytes();
String encodedString = Base64.encodeToString(bytes, Base64.NO_WRAP);
byte[] decodedBytes = Base64.decode(encodedString, Base64.NO_WRAP);
21
Aleksandar Ilic

今、私も別の解決策を見つけました...

    public class NewEncrypterTest
    {
        @Test
        public void canEncryptAndDecrypt() throws Exception
        {
            String toEncrypt = "FOOBAR";

            NewEncrypter encrypter = new NewEncrypter();

            byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
            String encoded = String.valueOf(Hex.encodeHex(encryptedByteArray));

            byte[] byteArrayToDecrypt = Hex.decodeHex(encoded.toCharArray());
            String decryptedText = encrypter.decrypt(byteArrayToDecrypt); 

            System.out.println("decryptedText:" + decryptedText);

            assertEquals(toEncrypt, decryptedText);
        }
    }
5
Bevor

問題は、任意のバイト配列からUTF-16(またはその他のエンコーディング)文字列を構築できないことです(ウィキペディアの TF-16 を参照)。ただし、暗号化されたバイト配列を損失することなくシリアル化および逆シリアル化して、たとえば永続化して後で使用するのはユーザー次第です。以下は、バイト配列で実際に何が起こっているかについての洞察を提供する修正されたクライアントコードです。

_public static void main(String[] args) throws Exception {
  String toEncrypt = "FOOBAR";

  NewEncrypter encrypter = new NewEncrypter();

  byte[] encryptedByteArray = encrypter.encrypt(toEncrypt);
  System.out.println("encryptedByteArray:" + Arrays.toString(encryptedByteArray));

  String decoded = new String(encryptedByteArray, "UTF-16");
  System.out.println("decoded:" + decoded);

  byte[] encoded = decoded.getBytes("UTF-16");
  System.out.println("encoded:" + Arrays.toString(encoded));

  String decryptedText = encrypter.decrypt(encryptedByteArray); // NOT the "encoded" value!
  System.out.println("decryptedText:" + decryptedText);
}
_

これは出力です:

_encryptedByteArray:[90, -40, -39, -56, -90, 51, 96, 95, -65, -54, -61, 51, 6, 15, -114, 88]
decoded:<some garbage>
encoded:[-2, -1, 90, -40, -1, -3, 96, 95, -65, -54, -61, 51, 6, 15, -114, 88]
decryptedText:FOOBAR
_

元のdecryptedTextから復元した場合、encryptedByteArrayは正しいです。 byte[] -> String("UTF-16")->byte[]変換中のデータ損失のため、encoded値はencryptedByteArrayと同じではないことに注意してください。

0