Początkowe bajty są niepoprawne po odszyfrowaniu Java AES / CBC


116

Co jest nie tak z następującym przykładem?

Problem polega na tym, że pierwsza część odszyfrowanego ciągu jest nonsensowna. Jednak reszta jest w porządku, dostaję ...

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}

48
NIE UŻYWAJ ŻADNEJ ODPOWIEDZI NA TO PYTANIE W POWAŻNYM PROJEKCIE! Wszystkie przykłady podane w tym pytaniu są podatne na wypełnianie wyroczni i ogólnie są bardzo złe w użyciu kryptografii. Wprowadzisz poważną lukę w zabezpieczeniach kryptografii w swoim projekcie, używając dowolnego z poniższych fragmentów.
HoLyVieR

16
@HoLyVieR, W odniesieniu do następujących cytatów: „Nie powinieneś tworzyć własnej biblioteki kryptograficznej” i „używać interfejsu API wysokiego poziomu, który zapewnia Twoja struktura”. Nikt tutaj nie rozwija własnej biblioteki kryptograficznej. Po prostu korzystamy z już istniejącego API wysokiego poziomu, które zapewnia framework java. Pan jest szalenie niedokładny.
k170

10
@MaartenBodewes, To, że oboje się zgadzacie, nie oznacza, że ​​oboje macie rację. Dobrzy programiści znają różnicę między opakowaniem interfejsu API wysokiego poziomu a przepisaniem interfejsu API niskiego poziomu. Dobrzy czytelnicy zauważą, że OP poprosił o „prosty przykład szyfrowania / odszyfrowania AES w języku java” i właśnie to otrzymał . Nie zgadzam się też z innymi odpowiedziami, dlatego też zamieściłem własną odpowiedź. Może powinniście spróbować tego samego i oświecić nas wszystkich swoją wiedzą.
k170,

6
@HoLyVieR To naprawdę najbardziej absurdalna rzecz, jaką kiedykolwiek czytałem w SO! Kim jesteś, żeby mówić ludziom, co mogą, a czego nie mogą się rozwijać?
TedTrippin

14
Nadal nie widzę przykładów @HoLyVieR. Zobaczmy kilka, czy wskazówki do bibliotek? Wcale nie konstruktywne.
danieljimenez

Odpowiedzi:


245

Wiele osób, w tym ja, napotyka wiele problemów podczas wykonywania tej pracy z powodu braku niektórych informacji, takich jak zapomnienie o konwersji do Base64, wektorach inicjalizacyjnych, zestawie znaków itp. Pomyślałem więc o stworzeniu w pełni funkcjonalnego kodu.

Mam nadzieję, że przyda się to wszystkim: aby skompilować, potrzebujesz dodatkowego jar Apache Commons Codec, który jest dostępny tutaj: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}

47
Jeśli nie chcesz polegać na 3rd party Apache Commons Codec biblioteki można użyć JDK za javax.xml.bind.DatatypeConverter do wykonywania kodowania / dekodowania Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Czy używasz stałego IV ?!
vianna77

36
Java 8 ma już narzędzia Base64: java.util.Base64.getDecoder () i java.util.Base64.getEncoder ()
Hristo Stoyanov

11
IV nie musi być tajne, ale musi być nieprzewidywalne dla trybu CBC (i unikalne dla CTR). Można go wysłać wraz z szyfrogramem. Powszechnym sposobem na to jest dodawanie przedrostka IV do tekstu zaszyfrowanego i odcinanie go przed odszyfrowaniem. Powinien zostać wygenerowany przezSecureRandom
Artjom B.

6
Hasło nie jest kluczem. IV powinien być losowy.
Maarten Bodewes

40

Oto rozwiązanie bez Apache Commons Codec„s Base64:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Przykład użycia:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Wydruki:

Hello world!
դ;��LA+�ߙb*
Hello world!

5
To jest doskonale funkcjonalny przykład, podobnie jak @ chandpriyankara. Ale po co definiować podpis, encrypt(String)a nie encrypt(byte[] )? Szyfrowanie (również deszyfrowanie) jest procesem opartym na bajtach (tak czy inaczej AES). Szyfrowanie przyjmuje bajty jako dane wejściowe i wyprowadza bajty, podobnie jak deszyfrowanie (przykład: Cipherobiekt robi). Otóż, jednym szczególnym przypadkiem użycia może być zaszyfrowanie bajtów pochodzących ze Stringa lub wysłanie ich jako String (załącznik MIME base64 dla poczty ...), ale jest to kwestia kodowania bajtów, dla których istnieją setki rozwiązania całkowicie niezwiązane z AES / szyfrowaniem.
GPI

3
@GPI: Tak, ale uważam, że jest to bardziej przydatne, Stringsponieważ w zasadzie pracuję w 95% przypadków, a ty i tak konwertujesz.
BullyWiiPlaza

9
Nie, to nie jest równoznaczne z kodem chandpriyankara! Twój kod używa EBC, który jest generalnie niebezpieczny i niepożądany. Powinien jawnie określać CBC. Po określeniu CBC kod się zepsuje.
Dan

Idealnie funkcjonalny, całkowicie niepewny i wykorzystujący bardzo złe praktyki programistyczne. klasa jest źle nazwana. Rozmiar klucza nie jest wcześniej sprawdzany. Ale co najważniejsze, kod wykorzystuje niezabezpieczony tryb EBC, ukrywając problem w pierwotnym pytaniu . Wreszcie nie określa kodowania znaków, co oznacza, że ​​dekodowanie do tekstu może się nie powieść na innych platformach.
Maarten Bodewes

24

Wydaje mi się, że nie radzisz sobie właściwie z wektorem inicjalizacyjnym (IV). Minęło dużo czasu, odkąd ostatnio czytałem o AES, IV i łańcuchach bloków, ale twoja linia

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

nie wydaje się być w porządku. W przypadku AES, możesz myśleć o wektorze inicjującym jako o „stanie początkowym” instancji szyfru, a ten stan to trochę informacji, których nie możesz uzyskać z klucza, ale z rzeczywistego obliczenia szyfru szyfrującego. (Można by argumentować, że gdyby IV można było wyodrębnić z klucza, nie byłoby to przydatne, ponieważ klucz jest już przekazany instancji szyfru podczas fazy inicjalizacji).

Dlatego powinieneś otrzymać IV jako bajt [] z instancji szyfru na końcu twojego szyfrowania

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

i powinieneś zainicjować swój Cipherin DECRYPT_MODEtym bajtem []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Następnie odszyfrowanie powinno być OK. Mam nadzieję że to pomoże.


Dzięki za pomoc nowicjuszowi. Ten przykład wybrałem z innych postów. Nie sądzę, że wiesz, jak uniknąć konieczności stosowania IV? Widziałem, ale nie próbowałem, inne przykłady AES, które go nie używają.
TedTrippin

Zignoruj ​​to, znalazłem odpowiedź! Muszę używać AES / ECB / PKCS5Padding.
TedTrippin

20
W większości przypadków nie chcesz korzystać z EBC. Po prostu wygoogluj dlaczego.
João Fernandes

2
@Mushy: zgodził się, że wybranie i jawne ustawienie IV z zaufanego losowego źródła jest lepsze niż pozwolenie instancji Cihper na pobranie go. Z drugiej strony, ta odpowiedź dotyczy pierwotnego problemu pomieszania wektora inicjalizacji dla klucza. Dlatego początkowo głosowano za nim. Teraz ten post stał się bardziej przykładowym kodem, do którego można się udać, a ludzie tutaj zrobili świetny przykład - tuż obok tego, czego dotyczyło pierwotne pytanie.
GPI

3
@GPI Upvoted. Inne „wspaniałe przykłady” nie są aż tak wspaniałe i tak naprawdę wcale nie odnoszą się do tego pytania. Zamiast tego wydaje się, że było to miejsce dla początkujących, którzy mogli ślepo kopiować próbki kryptograficzne bez zrozumienia, że ​​mogą istnieć możliwe problemy z bezpieczeństwem - i, jak zawsze, istnieją.
Maarten Bodewes

17

IV, którego używasz do odszyfrowania, jest nieprawidłowy. Zastąp ten kod

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Z tym kodem

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

I to powinno rozwiązać twój problem.


Poniżej znajduje się przykład prostej klasy AES w Javie. Nie polecam używania tej klasy w środowiskach produkcyjnych, ponieważ może ona nie uwzględniać wszystkich specyficznych potrzeb aplikacji.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

        try
        {
            final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Zauważ, że AES nie ma nic wspólnego z kodowaniem, dlatego zdecydowałem się obsługiwać go osobno i bez potrzeby jakichkolwiek bibliotek innych firm.


Po pierwsze, nie odpowiedziałeś na pierwotne pytanie. Po drugie, dlaczego odpowiadasz na już udzielone, dobrze przyjęte pytanie? Myślałem, że ochrona miała zatrzymać ten spam.
TedTrippin

14
Podobnie jak zaakceptowana odpowiedź, zdecydowałem się odpowiedzieć na twoje pytanie na przykładzie. Udostępniłem w pełni funkcjonalny fragment kodu, który pokazuje między innymi, jak prawidłowo radzić sobie z wektorem inicjalizacyjnym. Jeśli chodzi o drugie pytanie, czułem, że potrzebna jest zaktualizowana odpowiedź, ponieważ kodek Apache nie jest już potrzebny. Więc nie, to nie jest spam. Przestań się potykać.
k170

7
IV ma konkretny cel, który ma losowy szyfrogram i zapewnić bezpieczeństwo semantyczne. Jeśli użyjesz tej samej pary klucz + IV, atakujący mogą określić, czy wysłałeś wiadomość z tym samym prefiksem co wcześniej. IV nie musi być tajemnicą, ale musi być nieprzewidywalne. Powszechnym sposobem jest po prostu dodanie przedrostka IV do tekstu zaszyfrowanego i odcięcie go przed odszyfrowaniem.
Artjom B.,

4
głos negatywny: zakodowany IV, zobacz komentarz Artjoma B. powyżej, dlaczego jest źle
Murmel

1
Tryb CTR powinien być połączony z NoPadding. Tryb CTR z pewnością nie jest wymagany zamiast CBC (chyba że mają zastosowanie wyrocznie wypełnione), ale jeśli używany jest CTR , użyj "/NoPadding". CTR to tryb, który zamienia AES w szyfr strumieniowy, a szyfr strumieniowy działa na bajtach zamiast blokach.
Maarten Bodewes

16

W tej odpowiedzi zdecydowałem się podejść do głównego tematu „Prosty przykład szyfrowania / odszyfrowania w języku Java AES”, a nie do konkretnego pytania dotyczącego debugowania, ponieważ myślę, że przyniesie to korzyści większości czytelników.

To jest proste podsumowanie mojego wpisu na blogu na temat szyfrowania AES w Javie, więc polecam przeczytać go przed wdrożeniem czegokolwiek. Podam jednak prosty przykład do użycia i kilka wskazówek, na co należy uważać.

W tym przykładzie wybiorę użycie uwierzytelnionego szyfrowania w trybie Galois / Counter lub GCM . Powodem jest to, że w większości przypadków zależy Ci na uczciwości i autentyczności w połączeniu z poufnością (czytaj więcej na blogu ).

Samouczek szyfrowania / deszyfrowania AES-GCM

Oto kroki wymagane do szyfrowania / odszyfrowywania za pomocą AES-GCM z architekturą Java Cryptography Architecture (JCA) . Nie mieszaj z innymi przykładami , ponieważ subtelne różnice mogą spowodować, że Twój kod będzie całkowicie niepewny.

1. Utwórz klucz

Ponieważ zależy to od twojego przypadku użycia, przyjmuję najprostszy przypadek: losowy tajny klucz.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Ważny:

2. Utwórz wektor inicjujący

Wektor inicjujący (IV) jest używany tak, że ten sam klucz tajny będzie tworzyć różne teksty szyfr .

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Ważny:

3. Szyfruj za pomocą IV i klucza

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Ważny:

  • użyj 16-bajtowego / 128-bitowego znacznika uwierzytelniającego (używanego do weryfikacji integralności / autentyczności)
  • tag uwierzytelniający zostanie automatycznie dołączony do zaszyfrowanego tekstu (w implementacji JCA)
  • ponieważ GCM zachowuje się jak szyfr strumieniowy, nie jest wymagane dopełnianie
  • użyj CipherInputStreampodczas szyfrowania dużych fragmentów danych
  • chcesz sprawdzić dodatkowe (nie tajne) dane, jeśli zostały zmienione? Możesz użyć powiązanych danych z cipher.updateAAD(associatedData); Więcej tutaj.

3. Serializuj do pojedynczej wiadomości

Wystarczy dołączyć IV i zaszyfrowany tekst. Jak wspomniano powyżej, IV nie musi być tajne.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

Opcjonalnie zakoduj za pomocą Base64, jeśli potrzebujesz reprezentacji ciągu. Użyj wbudowanej implementacji Androida lub Java 8 (nie używaj kodeka Apache Commons - to okropna implementacja). Kodowanie służy do „konwersji” tablic bajtowych na reprezentację łańcuchową, aby zapewnić bezpieczeństwo ASCII, np .:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Przygotuj deszyfrowanie: deserializacja

Jeśli zakodowałeś wiadomość, najpierw zdekoduj ją do tablicy bajtów:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Ważny:

5. Odszyfruj

Zainicjuj szyfr i ustaw te same parametry, co przy szyfrowaniu:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Ważny:

  • nie zapomnij dodać powiązanych danych z, cipher.updateAAD(associatedData);jeśli dodałeś je podczas szyfrowania.

W tym streszczeniu można znaleźć działający fragment kodu.


Zauważ, że najnowsze implementacje Androida (SDK 21+) i Java (7+) powinny mieć AES-GCM. Starsze wersje mogą go nie mieć. Nadal wybieram ten tryb, ponieważ jest łatwiejszy do wdrożenia i jest bardziej wydajny w porównaniu do podobnego trybu Encrypt-then-Mac (z np. AES-CBC + HMAC ). Zobacz ten artykuł o tym, jak wdrożyć AES-CBC z HMAC .


Problem polega na tym, że proszenie o przykłady jest wyraźnie poza tematem w SO. A większym problemem jest to, że są to niesprawdzone fragmenty kodu, które są trudne do zweryfikowania. Doceniam wysiłek, ale nie sądzę, aby SO powinno być odpowiednim miejscem do tego.
Maarten Bodewes

1
Podziwiam jednak wysiłek, więc wskażę tylko jeden błąd: „iv musi być nieprzewidywalny w połączeniu z unikalnością (tj. Używaj losowego iv)” - to prawda dla trybu CBC, ale nie dla GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMmasz na myśli całą część, czy tylko to, że faktycznie nie musi być nieprzewidywalne?
Patrick Favre

1
„Jeśli nie rozumiesz tematu, to prawdopodobnie nie powinieneś przede wszystkim używać prymitywów niskiego poziomu”. Pewnie, że POWINNO tak być, wielu programistów nadal to robi. Nie jestem pewien, czy powstrzymać się od umieszczania wysokiej jakości treści dotyczących bezpieczeństwa / kryptografii w miejscach, w których często nie ma ich zbyt wiele, jest właściwym rozwiązaniem. - dzięki za wskazanie mojego błędu przy okazji
Patrick Favre

1
OK, tylko dlatego, że podoba mi się odpowiedź w treści (a nie cel): obsługę IV można uprościć, zwłaszcza podczas deszyfrowania: Java mimo wszystko ułatwia tworzenie IV bezpośrednio z istniejącej tablicy bajtów. To samo dotyczy deszyfrowania, które nie musi zaczynać się od offsetu 0. Całe to kopiowanie nie jest po prostu konieczne. Również jeśli musisz wysłać długość dla IV (prawda?), To dlaczego nie użyć pojedynczego (bez znaku) bajtu - nie zamierzasz przekroczyć 255 bajtów dla IV, prawda?
Maarten Bodewes

2

Wersja do uruchamiania edytora online: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}

Fajnie, szczęśliwie, że pomogło!
Bhupesh Pant

Hasło nie jest kluczem, IV nie powinien być statyczny. Ciągle wpisywany kod, który uniemożliwia zniszczenie klucza. Brak wskazania, co zrobić z IV, ani żadnego pomysłu, że powinien być nieprzewidywalny.
Maarten Bodewes

1

Często dobrym pomysłem jest skorzystanie z rozwiązania dostarczanego przez bibliotekę standardową:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Spowoduje to wyświetlenie tekstu „Tekst do zakodowania”.

Rozwiązanie jest oparte na podręczniku Java Cryptography Architecture Reference Guide i https://stackoverflow.com/a/20591539/146745 answer.


5
Nigdy nie używaj trybu EBC. Kropka.
Konstantino Sparakis

1
Nie należy używać EBC, jeśli szyfrujesz więcej niż jeden blok danych tym samym kluczem, więc opcja „Tekst do zakodowania” jest wystarczająca. stackoverflow.com/a/1220869/146745
andrej

Klucz @AndroidDev jest generowany w sekcji Przygotuj klucz: aesKey = keygen.generateKey ()
andrej

1

To poprawa w stosunku do zaakceptowanej odpowiedzi.

Zmiany:

(1) Używając losowego IV i dołącz go do zaszyfrowanego tekstu

(2) Używanie SHA-256 do generowania klucza na podstawie hasła

(3) Brak zależności od Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}

Skrót nadal nie jest funkcją generowania klucza opartą na haśle / PBKDF. Albo używasz losowego klucza, albo używasz PBKDF, takiego jak PBKDF2 / szyfrowanie oparte na haśle.
Maarten Bodewes

@MaartenBodewes Czy możesz zaproponować poprawę?
wvdz

PBKDF2 jest obecny w Javie, więc myślę, że właśnie zasugerowałem. OK, nie zakodowałem żadnego , ale moim zdaniem jest to trochę za dużo. Istnieje wiele przykładów szyfrowania opartego na haśle.
Maarten Bodewes

@MaartenBodewes Pomyślałem, że to może być prosta poprawka. Z ciekawości, jakie byłyby konkretne luki w zabezpieczeniach przy użyciu tego kodu w obecnej postaci?
wvdz

0

Kolejne rozwiązanie wykorzystujące java.util.Base64 z Spring Boot

Klasa szyfrująca

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

Klasa EncryptorController

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Przykład

http: // localhost: 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza


-1

Zoptymalizowana wersja zaakceptowanej odpowiedzi.

  • brak bibliotek innych firm

  • włącza IV do zaszyfrowanej wiadomości (może być publiczna)

  • hasło może mieć dowolną długość

Kod:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Stosowanie:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Przykładowe dane wyjściowe:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World

Twoja funkcja wyprowadzania hasła jest niebezpieczna. Nie spodziewałbym się e.printStackTrace()tak zwanego kodu zoptymalizowanego.
Maarten Bodewes
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.