Реализация Java-алгоритма TEA и выдача 32 битов без знака

Я пытаюсь реализовать алгоритм Tiny Encryption (TEA) в Java. Поскольку алгоритм делит каждый 64-битный блок на левый и правый субблоки, где каждый субблок имеет 32 целых числа без знака в соответствии с этим источником.

Как и ожидалось, я столкнулся с проблемой Java, не поддерживающей 32-разрядное целое число без знака. Я получал ошибки о числовом формате везде.

Поэтому я решил использовать BigInteger, который представил мне новую проблему. Исходя из моего понимания, поскольку TEA использует сдвиг и сложение 32-битных целых чисел, предполагается, что результат этих операций будет храниться в 32-битном виде, так что зашифрованный текст также будет 32-битным на субблок. Однако, со сдвигом и сложением, BigInteger не сохранил 32 бита на субблок. Действительно, я неожиданно получил очень большое количество бит в качестве вывода зашифрованного текста.

Поэтому я решил оставить BigInteger, реализуя сдвиг, сложение и вычитание в отдельных методах. К сожалению, я не получаю правильных результатов. Как показано ниже, расшифрованный зашифрованный текст не совпадает с исходным открытым текстом. Каково решение моей проблемы? Я получаю следующий вывод:

Original Plain Text:0x0123456789ABCDEF
CipherText:0xa0761126d09724fd
Decrypted CipherText is:0x8d5a4a234b3c6720

Ниже мой код.

import java.math.BigInteger;


public class TEA {

    BigInteger [] K ; //128 bits key
    private String plainText;
    public static final BigInteger delta = new BigInteger("9e3779b9",16);


    //constructor receives a string of plaintext and 128 bit key in hexadecimal
    public TEA(String plainText, String key)
    {
        parseKey(key);


    }

    //constructor receives a hexadecimal 
    public TEA(String key)
    {

        parseKey(key);

    }

    //parses a 128 bit key, given in hexadecimal form, and store its value in 4 integers (total of 128 bits), 
    private void parseKey(String key)
    {
        if(key.substring(0,2).equals("0x"))
            key= key.substring(2);

        //validating input
        if(key.length() != 32)
        {
            System.out.println("Invalid key size!");
            return;
        }


        //dividing the key into 4 strings
        String[] kStr = new String[4];
        int index=-1;
        for(int i=0; i<key.length(); i++)
        {
            if(i%8 == 0)
            {
                index++;
                kStr[index]="";

            }
            kStr[index] = kStr[index] + key.charAt(i);
        }


        //converting the 4 hex strings into 4 integers
        K= new BigInteger[4];
        for(int i=0; i<4; i++)
            K[i] = new BigInteger(kStr[i], 16); 

    }

    //receives a plaintext block of 64 bits in hexadecimal to be encrypted
    //returns the cipher block
    String encryptBlock(String plainTextBlock)
    {
        if(plainTextBlock.substring(0,2).equals("0x"))
            plainTextBlock= plainTextBlock.substring(2);

        //validating input
        if(plainTextBlock.length()!=16)
        {
            System.out.println("Invalid block size!");
            return null;

        }

        //separating the string block into left and right blocks
        String LStr = plainTextBlock.substring(0, 8); //left block (32 bit)
        String RStr = plainTextBlock.substring(8); //right block (32 bit)

        //converting left and right blocks to integers
        BigInteger L = new BigInteger(LStr, 16);
        BigInteger R = new BigInteger(RStr, 16);


        BigInteger sum= new BigInteger("0");
        //32 rounds
        for(int i=0; i<32; i++)
        {
            sum = sum.add(delta);
            L= sum(L,  (sum(shiftLeft(R,4),K[0]))   .xor(sum(R,sum))    .xor(sum(shiftRight(R,5),K[1]))) ;
            R= sum(R,  (sum(shiftLeft(L,4),K[2]))   .xor(sum(L,sum))    .xor(sum(shiftRight(L,5),K[3]))) ;

            //R= R.add(  (shiftLeft(R,4).add(K[2])).xor(L.add(sum)).xor(shiftRight(L,5).add(K[3])) );


        }

        //joining back the blocks as hex
        String cipherBlock = "0x"+L.toString(16)+R.toString(16)+"";


        return cipherBlock;
    }


    //receives a ciphertext block of 64 bits in hexadecimal to be decrypted
    //returns the plaintext block
    String decryptBlock(String cipherBlock)
    {
        if(cipherBlock.substring(0,2).equals("0x"))
            cipherBlock= cipherBlock.substring(2);

        //validating input
        if(cipherBlock.length()!=16)
        {
            System.out.println("Invalid block size!");
            return null;

        }

        //separating the string block into left and right blocks
        String LStr = cipherBlock.substring(0, 8); //left block (32 bit)
        String RStr = cipherBlock.substring(8); //right block (32 bit)

        //converting left and right blocks to integers
        BigInteger L = new BigInteger(LStr, 16);
        BigInteger R = new BigInteger(RStr, 16);

        BigInteger sum= shiftLeft(delta,5);
        //32 rounds
        for(int i=0; i<32; i++)
        {

            R= subtract(R,  (sum(shiftLeft(L,4),K[2]))   .xor(sum(L,sum))    .xor(sum(shiftRight(L,5),K[3]))) ;
            L= subtract(L,  (sum(shiftLeft(R,4),K[0]))   .xor(sum(R,sum))    .xor(sum(shiftRight(R,5),K[1]))) ;


            //R= R.subtract(  (L.shiftLeft(4).add(K[2])).xor(L.add(sum)).xor(L.shiftRight(5).add(K[3])) );
            //L= L.subtract(  (R.shiftLeft(4).add(K[0])).xor(R.add(sum)).xor(R.shiftRight(5).add(K[1])) );
            sum = sum.subtract(delta);  
        }

        //joining back the blocks as hex
        String plainTextBlock = "0x"+L.toString(16)+R.toString(16)+"";


        return plainTextBlock;
    }


    private BigInteger shiftLeft(BigInteger x, int steps)
    {

       BigInteger shifted=null;
       boolean negative =false;

       String xStr = x.toString(2);

       //removing negative sign while shifting (currently)
       if(xStr.charAt(0)=='-')
       {
           negative= true;
           xStr = xStr.substring(1);
       }


       int additionalSize = 32- xStr.length();

       for(int i=0; i<additionalSize; i++)
           xStr= "0"+xStr;



       for(int i=0; i<steps; i++)
       {
           xStr = xStr.substring(1);
           xStr = xStr+"0";
       }

       //one last addition of negative sign if the number is negative
       if(negative==true)
           xStr= "-"+xStr;

       //System.out.println(xStr);
      shifted = new BigInteger(xStr,2);

        return shifted;
    }


    private BigInteger shiftRight(BigInteger x, int steps)
    {
       BigInteger shifted=null;
       boolean negative = false;

       String xStr = x.toString(2);

       //removing negative sign while shifting (currently)
       if(xStr.charAt(0)=='-')
       {
           negative= true;
           xStr = xStr.substring(1);
       }

       int additionalSize = 32- xStr.length();

       for(int i=0; i<additionalSize; i++)
           xStr= "0"+xStr;


       for(int i=0; i<steps; i++)
       {
           xStr = xStr.substring(0,xStr.length()-1);
           xStr = "0"+xStr;
       }

       //one last addition of negative sign if the number is negative
       if(negative==true)
           xStr= "-"+xStr;

      shifted = new BigInteger(xStr,2);

        return shifted;
    }

    private BigInteger sum(BigInteger a, BigInteger b)
    {

        BigInteger sum = a.add(b);
        String sumStr = sum.toString(2);
        if(sumStr.length()>32)
        {
            int diff = sumStr.length()- 32;
            sumStr = sumStr.substring(diff);
        }

        BigInteger newSum = new BigInteger(sumStr,2);

        return newSum;
    }

    private BigInteger subtract(BigInteger a, BigInteger b)
    {

        BigInteger sub = a.subtract(b);

        String subStr = sub.toString(2);
        if(subStr.length()>32)
        {
            int diff = subStr.length()- 32;
            subStr = subStr.substring(diff);
        }

        BigInteger newSub = new BigInteger(subStr,2);

        return newSub;
    }



    public static void main(String[] args)
    {

        String plainText="0x0123456789ABCDEF";
        String key= "0xA56BABCD00000000FFFFFFFFABCDEF01";
        TEA tea = new TEA(key);
        String cipherText = tea.encryptBlock(plainText);
        System.out.println("Original Plain Text:"+plainText);
        System.out.println("CipherText:"+cipherText);
        System.out.println("Decrypted CipherText is:"+tea.decryptBlock(cipherText));




    }


}

1 ответ

Я не видел причин использовать BigIntegers, поэтому я попробовал свою собственную реализацию на Java, которая является почти дословной копией кода C в статье в Википедии. Это выглядит правильно для меня, хотя у меня нет тестов для проверки.

public class TEAToy {

    private final static int DELTA = 0x9e3779b9;
    private final static int DECRYPT_SUM_INIT = 0xC6EF3720;
    private final static long MASK32 = (1L << 32) - 1;
    public static long encrypt(long in, int [] k) {
        int v1 = (int) in;
        int v0 = (int) (in >>> 32);
        int sum = 0;
        for (int i=0; i<32; i++) {
            sum += DELTA;
            v0 += ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>>5) + k[1]);
            v1 += ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>>5) + k[3]);
        }

        return (v0 & MASK32) << 32 | (v1 & MASK32);

    }

    public static long decrypt(long in, int [] k) {
        int v1 = (int) in;
        int v0 = (int) (in >>> 32);
        int sum = DECRYPT_SUM_INIT;
        for (int i=0; i<32; i++) {
            v1 -= ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>>5) + k[3]);
            v0 -= ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>>5) + k[1]);
            sum -= DELTA;
        }
        return (v0 & MASK32) << 32 | (v1 & MASK32);
    }

Как видите, тот факт, что подписи в Java имеют незначительное значение.

Другие вопросы по тегам