Java AES / CBC şifre çözme işleminden sonra ilk baytlar yanlış


116

Aşağıdaki örnekte yanlış olan ne?

Sorun, şifresi çözülen dizenin ilk kısmının anlamsız olmasıdır. Ancak gerisi iyi, anlıyorum ...

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
CİDDİ PROJEDE BU SORU CEVAPLARINI KULLANMAYIN! Bu soruda verilen tüm örnekler, dolgu oracle'ına karşı savunmasızdır ve genel olarak çok kötü kriptografi kullanımıdır. Aşağıdaki parçalardan herhangi birini kullanarak projenizde ciddi kriptografi zafiyetini ortaya çıkaracaksınız.
HoLyVieR

16
@HoLyVieR, Şu alıntılarla ilgili olarak: "Kendi şifreleme kitaplığınızı geliştirmemelisiniz" ve "çerçevenizin sağladığı yüksek seviyeli bir API kullanın." Burada kimse kendi şifreleme kitaplığını geliştirmiyor. Sadece java çerçevesinin sağladığı mevcut, yüksek seviyeli API'yi kullanıyoruz. Bayım, çılgınca yanlışsınız.
k170

10
@MaartenBodewes, İkiniz de aynı fikirde olmanız ikinizin de haklı olduğu anlamına gelmez. İyi geliştiriciler, yüksek seviyeli bir API'yi sarma ile düşük seviyeli bir API'yi yeniden yazma arasındaki farkı bilir. İyi okuyucular, OP'nin "basit bir java AES şifreleme / şifre çözme örneği" istediğini fark edeceklerdir ve tam olarak elde ettiği şey budur . Diğer cevaplara da katılmıyorum, bu yüzden kendime bir cevap verdim. Belki de aynısını denemeli ve uzmanlığınızla hepimizi aydınlatmalısınız.
k170

6
@HoLyVieR Bu gerçekten SO'da okuduğum en saçma şey! İnsanlara neyi geliştirip geliştiremeyeceklerini söyleyecek kimsin?
TedTrippin

14
Hala örnek göremiyorum @ HoLyVieR. Bakalım bazılarını veya kitaplıklara işaret edenleri görelim mi? Hiç yapıcı değil.
danieljimenez

Yanıtlar:


245

Ben de dahil olmak üzere pek çok insan bu işi yaparken Base64'e dönüştürmeyi unutmak, başlatma vektörleri, karakter seti vb. Gibi bazı bilgilerin eksikliğinden dolayı birçok sorunla karşılaşıyor. Bu yüzden tamamen işlevsel bir kod yapmayı düşündüm.

Umarım bu hepiniz için yararlı olacaktır: Derlemek için ek Apache Commons Codec kavanozuna ihtiyacınız vardır, bu şurada bulunabilir: 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
Eğer JDK kullanabilirsiniz 3. parti Apache Commons Codec kütüphanesine bağımlı istemiyorsanız javax.xml.bind.DatatypeConverter : Base64 kodlama / kod çözme gerçekleştirmek için System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Sabit bir IV kullanıyor musunuz ?!
vianna77

36
Java 8 zaten Base64 araçlarına sahip: java.util.Base64.getDecoder () ve java.util.Base64.getEncoder ()
Hristo Stoyanov

11
IV'ün gizli olması gerekmez, ancak CBC modu için tahmin edilemez (ve CTR için benzersiz) olması gerekir. Şifreli metin ile birlikte gönderilebilir. Bunu yapmanın yaygın bir yolu, IV'ü şifreli metne önek koymak ve şifre çözmeden önce onu dilimlemektir. Bu aracılığıyla oluşturulan edilmelidirSecureRandom
Artjom B 'ye

6
Parola, anahtar değildir. Bir IV rastgele olmalıdır.
Maarten Bodewes

40

İşte Apache Commons Codec' s'siz bir çözüm 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);
    }
}

Kullanım örneği:

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));

Baskılar:

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

5
Bu, @ chandpriyankara'nınki gibi mükemmel işlevsel bir örnektir. Ama neden bir imza tanımlamak encrypt(String)değil encrypt(byte[] )?. Şifreleme (şifre çözme de) bayt tabanlı bir işlemdir (AES zaten). Şifreleme, girdi olarak bayt alır ve bayt çıktı verir, aynı şekilde şifre çözme de yapar (noktada: Ciphernesne alır). Şimdi, belirli bir kullanım durumu, bir Dizeden gelen şifrelenmiş baytlara sahip olmak veya bir Dize olarak gönderilmek olabilir (bir Posta için base64 MIME eki ...), ancak bu, yüzlerce bayt var olan bir kodlama baytı sorunudur. AES / şifreleme ile tamamen ilgisiz çözümler.
GPI

3
@GPI: Evet, ama ben bunu daha kullanışlı buluyorum Stringsçünkü temelde% 95 ile çalıştığım şey bu ve yine de dönüşüm gerçekleştiriyorsun.
BullyWiiPlaza

9
Hayır, bu Chandpriyankara'nın koduna eşdeğer değildir! Kodunuz genellikle güvensiz olan ve istenmeyen ECB'yi kullanıyor. CBC'yi açıkça belirtmelidir. CBC belirtildiğinde, kodunuz kırılır.
Dan

Mükemmel işlevsel, tamamen güvensiz ve çok kötü programlama uygulamaları kullanıyor. sınıfın adı kötü. Anahtar boyutu önceden kontrol edilmemiştir. Ancak en önemlisi, kod , sorunu orijinal soruda gizleyerek güvenli olmayan ECB modunu kullanır . Son olarak, bir karakter kodlaması belirtmez, yani metne kod çözme diğer platformlarda başarısız olabilir.
Maarten Bodewes

24

Bana Başlatma Vektörünüz (IV) ile düzgün bir şekilde ilgilenmiyorsunuz gibi görünüyor. AES, IV'ler ve blok zincirleme hakkında en son okuduğumdan bu yana uzun zaman geçti, ancak

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

iyi görünmüyor. AES durumunda, başlatma vektörünü bir şifre örneğinin "başlangıç ​​durumu" olarak düşünebilirsiniz ve bu durum, şifreleme şifresinin gerçek hesaplamasından değil, anahtarınızdan alamayacağınız bir bilgi parçasıdır. (Biri, eğer IV anahtardan çıkarılabiliyorsa, o zaman anahtarın başlatma aşamasında zaten şifre örneğine verildiğinden hiçbir faydası olmayacağı iddia edilebilir).

Bu nedenle, şifrelemenizin sonunda şifre örneğinden IV'ü bir bayt [] olarak almalısınız.

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

ve size başlatmak gerektiğini Cipherin DECRYPT_MODE] [Bu byte ile:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Ardından, şifre çözme işleminiz tamam olmalıdır. Bu yardımcı olur umarım.


Yeni başlayan birine yardım ettiğiniz için teşekkürler. Bu örneği diğer gönderilerden kaldırdım. IV ihtiyacından nasıl kaçınacağınızı bildiğinizi sanmıyorum? Kullanmayan diğer AES örneklerini gördüm ama denemedim.
TedTrippin

Görmezden gelin, cevabı buldum! AES / ECB / PKCS5Padding kullanmam gerekiyor.
TedTrippin

20
Çoğu kez yok ECB kullanmak istiyorum. Sadece google neden.
João Fernandes

2
@Mushy: Güvenilir rastgele bir kaynaktan bir IV seçmenin ve açıkça ayarlamanın, Cihper örneğinin bir tane almasına izin vermekten daha iyi olduğunu kabul etti. Öte yandan, bu yanıt, anahtar için başlatma vektörünün karıştırılmasına ilişkin orijinal sorunu ele almaktadır. Bu yüzden ilk başta olumlu oy verildi. Şimdi, bu gönderi daha çok örnek bir kod noktası haline geldi ve buradaki insanlar harika bir örnek oluşturdu - asıl sorunun ne hakkında olduğunun yanı sıra.
GPI

3
@GPI Upvoted. Diğer "harika örnekler" o kadar da harika değil ve aslında soruyu hiç ele almıyorlar. Bunun yerine, yeni başlayanların olası güvenlik sorunlarının olabileceğini ve her zamanki gibi olduğunu anlamadan körü körüne kriptografik örnekleri kopyaladıkları bir yer gibi görünüyor.
Maarten Bodewes

17

Şifre çözme için kullandığınız IV yanlış. Bu kodu değiştirin

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

Bu kod ile

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

Ve bu senin problemini çözmeli.


Aşağıda, Java'da basit bir AES sınıfı örneği yer almaktadır. Uygulamanızın tüm özel ihtiyaçlarını hesaba katmayabileceğinden, bu sınıfı üretim ortamlarında kullanmanızı önermiyorum.

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)));
    }
}

AES'in kodlamayla hiçbir ilgisi olmadığını unutmayın, bu yüzden onu ayrı olarak ve herhangi bir üçüncü taraf kitaplığına ihtiyaç duymadan ele almayı seçtim.


Öncelikle, asıl soruyu cevaplamadınız. İkincisi, neden önceden cevaplanmış, iyi kabul edilmiş bir soruyu yanıtlıyorsunuz? Korumanın bu spam'i durdurması gerektiğini sanıyordum.
TedTrippin

14
Kabul edilen cevap gibi, sorunuzu örnek yoluyla cevaplamayı seçtim. Diğer şeylerin yanı sıra, başlatma vektörüyle nasıl düzgün bir şekilde başa çıkılacağını gösteren tamamen işlevsel bir kod parçası sağladım. İkinci sorunuza gelince, Apache codec bileşeni artık gerekli olmadığından güncellenmiş bir cevaba ihtiyaç olduğunu hissettim. Yani hayır, bu spam değil. Düşmeyi bırak.
k170

7
Bir IV'ün şifreli metni rastgele hale getirmek ve anlamsal güvenlik sağlamak gibi belirli bir amacı vardır . Aynı anahtar + IV çiftini kullanırsanız, saldırganlar öncekiyle aynı öneke sahip bir ileti gönderip göndermediğinizi belirleyebilir. IV'ün gizli olması gerekmez, ancak tahmin edilemez olması gerekir. Yaygın bir yol, IV'ü şifreli metne eklemek ve şifre çözmeden önce kesmektir.
Artjom B.

4
aşağı oy: sabit kodlu IV, yukarıdaki Artjom B. yorumuna bakın neden kötü
Murmel

1
CTR modu NoPadding ile eşleştirilmelidir. TO modu kesinlikle TO if (dolgu kahinler uygulamadığınız sürece), ancak CBC yerine gereklidir edilir kullanılan, daha sonra kullanmak "/NoPadding". CTR, AES'yi bir akış şifresinde çeviren bir moddur ve bir akış şifresi, bloklar yerine baytlar üzerinde çalışır.
Maarten Bodewes

16

Bu yanıtta, "Basit Java AES şifreleme / şifre çözme örneği" ana temasına yaklaşmayı seçiyorum, özel hata ayıklama sorusuna değil çünkü bunun okuyucuların çoğuna fayda sağlayacağını düşünüyorum.

Bu, Java'da AES şifreleme hakkındaki blog yazımın basit bir özetidir, bu yüzden herhangi bir şey uygulamadan önce onu okumanızı tavsiye ederim. Bununla birlikte, yine de kullanmak için basit bir örnek sunacağım ve nelere dikkat edileceğine dair bazı ipuçları vereceğim.

Bu örnekte , Galois / Counter Mode veya GCM modu ile kimliği doğrulanmış şifreleme kullanmayı seçeceğim . Bunun nedeni, çoğu durumda gizlilikle birlikte bütünlük ve özgünlük istemenizdir ( blogda daha fazlasını okuyun ).

AES-GCM Şifreleme / Şifre Çözme Eğitimi

Java Cryptography Architecture (JCA) ile AES-GCM ile şifreleme / şifre çözme için gerekli adımlar aşağıda verilmiştir . Diğer örneklerle karıştırmayın farklılıklar kodunuzu tamamen güvensiz hale getirebileceğinden .

1. Anahtar Oluşturun

Kullanım durumunuza bağlı olduğu için, en basit durumu kabul edeceğim: rastgele bir gizli anahtar.

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

Önemli:

2. Başlatma Vektörünü Oluşturun

Aynı gizli anahtarın farklı şifre metinleri oluşturması için bir başlatma vektörü (IV) kullanılır .

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

Önemli:

3. IV ve Anahtar ile şifreleyin

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);

Önemli:

  • 16 bayt / 128 bit kimlik doğrulama etiketi kullan (bütünlüğü / özgünlüğü doğrulamak için kullanılır)
  • kimlik doğrulama etiketi otomatik olarak şifre metnine eklenecektir (JCA uygulamasında)
  • GCM bir akış şifresi gibi davrandığından, herhangi bir dolgu gerekmez
  • kullanım CipherInputStreambüyük veri yığınlarını şifrelerken
  • değiştirilmişse ek (gizli olmayan) verilerin kontrol edilmesini ister misiniz? Buradaki Diğer ile ilişkili verileri kullanmak isteyebilirsiniz cipher.updateAAD(associatedData); .

3. Tek Mesaja Seri Hale Getirin

Sadece IV ve şifreli metni ekleyin. Yukarıda belirtildiği gibi, IV'ün gizli olmasına gerek yoktur.

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

Bir dize gösterimine ihtiyacınız varsa isteğe bağlı olarak Base64 ile kodlayın . Ya Android'in ya da Java 8'in yerleşik uygulamasını kullanın (Apache Commons Codec'i kullanmayın - bu çok kötü bir uygulama). Kodlama, bayt dizilerini ASCII güvenli hale getirmek için dize gösterimine "dönüştürmek" için kullanılır, örneğin:

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

4. Şifre Çözmeyi Hazırlayın: Seriyi Kaldır

Mesajı kodladıysanız, önce onu bayt dizisi olarak çözün:

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

Önemli:

5. Şifresini çöz

Şifreyi başlatın ve şifreleme ile aynı parametreleri ayarlayın:

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);

Önemli:

  • eklemeyi unutmayın ilişkili verileri ile cipher.updateAAD(associatedData);şifreleme sırasında eklenirse.

Bu özetin içinde çalışan bir kod parçacığı bulunabilir.


En son Android (SDK 21+) ve Java (7+) uygulamalarının AES-GCM'ye sahip olması gerektiğini unutmayın. Daha eski sürümler eksik olabilir. Yine de bu modu seçiyorum, çünkü benzer Mac'ten Şifrele moduna (örneğin AES-CBC + HMAC ile ) kıyasla daha verimli olmasının yanı sıra uygulanması daha kolay . AES-CBC'nin HMAC ile nasıl uygulanacağına ilişkin bu makaleye bakın .


Sorun şu ki, örnek istemek, SO konusunda açıkça konu dışı. Ve daha büyük sorun, bunların doğrulanması zor, incelenmemiş kod parçaları olmasıdır. Çabanızı takdir ediyorum, ancak bunun için SO'nun olması gerektiğini düşünmüyorum.
Maarten Bodewes

1
Yine de çabayı takdir ediyorum, bu yüzden tek bir hataya işaret edeceğim: "iv benzersiz olmakla birlikte tahmin edilemez olmalıdır (yani rastgele iv kullanın)" - bu CBC modu için doğrudur, ancak GCM için değildir.
Maarten Bodewes

this is true for CBC mode but not for GCMtüm kısmı mı kastediyorsunuz yoksa sadece tahmin edilemez olması gerekmiyor mu?
Patrick Favre

1
"Eğer konuyu anlamadıysanız, muhtemelen ilk etapta düşük seviyeli ilkelleri kullanmamalısınız" tabii ki, durum böyle OLMALIDIR, birçok geliştirici hala bunu yapıyor. Bunun için doğru çözümün çoğu zaman fazla olmayan yerlerde güvenlik / kriptografi ile ilgili yüksek kaliteli içerikler koymaktan kaçınmanın emin değilim. - hatamı gösterdiğin için teşekkürler btw
Patrick Favre

1
Tamam, içeriğin cevabını sevdiğim için (amaçtan ziyade): IV işleme özellikle şifre çözme sırasında basitleştirilebilir: Java sonuçta doğrudan mevcut bir bayt dizisinden bir IV oluşturmayı kolaylaştırır. Aynısı şifre çözme için de geçerlidir, bu sıfır ofsetten başlamak zorunda değildir. Tüm bu kopyalama işlemleri basitçe gerekli değildir. Ayrıca eğer doğru, IV 255 bayt ötesine geçmesini etmeyeceğiz - Eğer IV (? Öyle mi) o zaman neden tek (imzasız) byte kullanmayan bir uzunluğa göndermek zorunda?
Maarten Bodewes

2

Online Editor Çalıştırılabilir sürüm: -

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")));
    }
}

Harika, yardımcı olduğu için mutluyum!
Bhupesh Pant

Parola bir anahtar değildir , IV sabit olmamalıdır. Hala sıkı yazılan kod, anahtarı yok etmeyi imkansız kılıyor. IV ile ne yapılacağına dair bir gösterge ya da öngörülemez olması gerektiğine dair herhangi bir fikir yok.
Maarten Bodewes

1

Kitaplık tarafından sağlanan standart çözüme güvenmek genellikle iyi bir fikirdir:

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));
}

Bu, "Kodlanacak metin" yazdırır.

Çözüm, Java Cryptography Architecture Referans Kılavuzu ve https://stackoverflow.com/a/20591539/146745 cevabına dayanmaktadır .


5
ECB modunu asla kullanmayın. Dönemi.
Konstantino Sparakis

1
Aynı anahtarla birden fazla veri bloğunun şifrelenmesi durumunda ECB kullanılmamalıdır, bu nedenle "Kodlanacak metin" için yeterince iyidir. stackoverflow.com/a/1220869/146745
andrej

@AndroidDev anahtarı, anahtarı hazırla bölümünde oluşturuldu: aesKey = keygen.generateKey ()
andrej

1

Bu, kabul edilen cevaba göre bir gelişmedir.

değişiklikler:

(1) Rastgele IV kullanarak ve şifrelenmiş metnin başına ekleyin

(2) Bir paroladan anahtar oluşturmak için SHA-256'yı kullanma

(3) Apache Commons'a bağımlılık yok

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);
}

Karma, hala bir şifre tabanlı anahtar oluşturma işlevi / PBKDF değildir. Ya rastgele bir anahtar kullanırsınız ya da PBKDF2 / Parola Tabanlı Şifreleme gibi bir PBKDF kullanırsınız.
Maarten Bodewes

@MaartenBodewes Bir iyileştirme önerebilir misiniz?
wvdz

PBKDF2 Java'da mevcut, bu yüzden sanırım bir tane önerdim. Tamam, ben yapmadım kod biri, ama bu en Bence çok fazla biraz soran. Parola Tabanlı Şifrelemenin birçok örneği vardır.
Maarten Bodewes

@MaartenBodewes Basit bir düzeltme olabileceğini düşündüm. Merak ettiğim için, bu kodu olduğu gibi kullanırken belirli güvenlik açıkları neler olabilir?
wvdz

0

Spring Boot ile java.util.Base64 kullanan başka bir çözüm

Şifreleme Sınıfı

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)));
    }
}

EncryptorController Sınıfı

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

Misal

http: // localhost: 8082 / şifre / şifrelemek / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / şifre / şifresini / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza


-1

Kabul edilen cevabın optimize edilmiş versiyonu.

  • 3. parti kitap yok

  • şifrelenmiş mesaja IV içerir (herkese açık olabilir)

  • şifre herhangi bir uzunlukta olabilir

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;
    }
}

Kullanımı:

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);

Örnek çıktı:

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

Parola türetme işleviniz güvensiz. e.printStackTrace()Sözde optimize edilmiş kod beklemiyordum .
Maarten Bodewes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.