Byte awal salah setelah dekripsi Java AES / CBC


116

Apa yang salah dengan contoh berikut?

Masalahnya adalah bahwa bagian pertama dari string yang didekripsi itu tidak masuk akal. Namun, sisanya baik-baik saja, saya mendapatkan ...

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
JANGAN GUNAKAN JAWABAN PERTANYAAN INI DALAM PROYEK SERIUS! Semua contoh yang diberikan dalam pertanyaan ini rentan terhadap padding oracle dan secara keseluruhan penggunaan kriptografi sangat buruk. Anda akan memperkenalkan kerentanan kriptografi yang serius dalam proyek Anda dengan menggunakan salah satu cuplikan di bawah ini.
HoLyVieR

16
@HoLyVieR, Mengenai kutipan berikut: "Anda tidak boleh mengembangkan pustaka kriptografi Anda sendiri" dan "menggunakan API tingkat tinggi yang disediakan kerangka kerja Anda". Tidak ada orang di sini yang mengembangkan perpustakaan kriptografinya sendiri. Kami hanya menggunakan API tingkat tinggi yang sudah ada yang disediakan oleh kerangka kerja java. Anda tuan sangat tidak akurat.
k170

10
@MaartenBodewes, Hanya karena Anda berdua setuju tidak berarti Anda berdua benar. Pengembang yang baik tahu perbedaan antara membungkus API tingkat tinggi dan menulis ulang API tingkat rendah. Pembaca yang baik akan melihat bahwa OP meminta "contoh enkripsi / dekripsi java AES sederhana" dan itulah yang dia dapatkan . Saya juga tidak setuju dengan jawaban yang lain, itulah sebabnya saya memposting jawaban saya sendiri. Mungkin kalian harus mencoba hal yang sama dan mencerahkan kami semua dengan keahlian kalian.
k170

6
@HoLyVieR Itu benar-benar hal paling absurd yang pernah saya baca di SO! Siapa Anda untuk memberi tahu orang apa yang mereka bisa dan tidak bisa kembangkan?
TedTrippin

14
Saya masih tidak melihat contoh @HoLyVieR. Mari kita lihat beberapa, atau petunjuk ke perpustakaan? Tidak konstruktif sama sekali.
danieljimenez

Jawaban:


245

Banyak orang termasuk saya menghadapi banyak masalah dalam membuat ini berfungsi karena kehilangan beberapa informasi seperti, lupa mengonversi ke Base64, vektor inisialisasi, kumpulan karakter, dll. Jadi saya berpikir untuk membuat kode yang berfungsi penuh.

Semoga bermanfaat bagi Anda semua: Untuk mengkompilasi Anda memerlukan jar Codec Apache Commons tambahan, yang tersedia di sini: 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
Jika Anda tidak ingin bergantung pada pustaka Apache Commons Codec pihak ketiga, Anda dapat menggunakan javax.xml.bind.DatatypeConverter JDK untuk melakukan encoding / decoding Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0

8
Apakah Anda menggunakan IV konstan ?!
vianna77

36
Java 8 sudah memiliki alat Base64: java.util.Base64.getDecoder () dan java.util.Base64.getEncoder ()
Hristo Stoyanov

11
IV tidak harus bersifat rahasia, tetapi harus tidak dapat diprediksi untuk mode CBC (dan unik untuk CTR). Ini dapat dikirim bersama dengan ciphertext. Cara umum untuk melakukan ini adalah dengan mengawali IV ke ciphertext dan memotongnya sebelum dekripsi. Ini harus dihasilkan melaluiSecureRandom
Artjom B.

6
Kata sandi bukanlah kunci. IV harus acak.
Maarten Bodewes

40

Berikut solusi tanpa 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);
    }
}

Contoh penggunaan:

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

Cetakan:

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

5
Ini adalah contoh yang sangat fungsional, sama seperti @ chandpriyankara. Tapi mengapa mendefinisikan tanda tangan encrypt(String)dan bukan encrypt(byte[] )?. Enkripsi (dekripsi juga) adalah proses berbasis byte (bagaimanapun juga AES). Enkripsi mengambil byte sebagai input, dan output byte, begitu pula dekripsi (contoh kasus: Cipherobjek melakukannya). Sekarang, satu kasus penggunaan tertentu mungkin memiliki byte terenkripsi yang berasal dari String, atau dikirim sebagai String (lampiran MIME base64 untuk Mail ...), tetapi itu adalah masalah encoding byte, yang di dalamnya terdapat ratusan solusi, sama sekali tidak terkait dengan AES / enkripsi.
GPI

3
@ GPI: Ya, tetapi saya merasa lebih berguna Stringskarena pada dasarnya itulah 95% saya bekerja dengan waktu dan Anda akhirnya tetap berkonversi.
BullyWiiPlaza

9
Tidak, ini tidak sama dengan kode chandpriyankara! Kode Anda menggunakan ECB yang umumnya tidak aman dan tidak diinginkan. Harus secara eksplisit menentukan CBC. Ketika CBC ditentukan, kode Anda akan rusak.
Dan

Berfungsi sempurna, sama sekali tidak aman, dan menggunakan praktik pemrograman yang sangat buruk. nama kelasnya buruk. Ukuran kunci tidak diperiksa sebelumnya. Namun yang terpenting, kode tersebut menggunakan mode ECB yang tidak aman, menyembunyikan masalah di pertanyaan awal . Terakhir, ini tidak menentukan pengkodean karakter, yang berarti bahwa decoding ke teks mungkin gagal di platform lain.
Maarten Bodewes

24

Menurut saya Anda tidak berurusan dengan benar dengan Inisialisasi Vektor (IV). Sudah lama sekali sejak terakhir kali saya membaca tentang AES, IV, dan rantai blok, tetapi kalimat Anda

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

sepertinya tidak baik-baik saja. Dalam kasus AES, Anda dapat menganggap vektor inisialisasi sebagai "status awal" dari cipher instance, dan status ini adalah sedikit informasi yang tidak dapat Anda peroleh dari kunci Anda, tetapi dari komputasi sebenarnya dari sandi yang mengenkripsi. (Seseorang dapat berargumen bahwa jika IV dapat diekstraksi dari kunci, maka itu tidak akan berguna, karena kunci tersebut sudah diberikan ke contoh cipher selama fase init).

Oleh karena itu, Anda harus mendapatkan IV sebagai byte [] dari cipher instance di akhir enkripsi Anda

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

dan Anda harus menginisialisasi Cipherin DECRYPT_MODEdengan byte ini []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Kemudian, dekripsi Anda akan baik-baik saja. Semoga ini membantu.


Terima kasih telah membantu seorang pemula. Saya membuat contoh ini dari posting lain. Saya kira Anda tidak tahu bagaimana menghindari kebutuhan akan infus? Saya telah melihat, tetapi belum mencoba, contoh AES lain yang tidak menggunakannya.
TedTrippin

Abaikan itu, saya telah menemukan jawabannya! Saya perlu menggunakan AES / ECB / PKCS5Padding.
TedTrippin

20
Sering kali Anda tidak ingin menggunakan ECB. Hanya google kenapa.
João Fernandes

2
@Mushy: setuju bahwa memilih dan secara eksplisit mengatur IV, dari sumber acak tepercaya, lebih baik daripada hanya membiarkan instance Cihper mengambilnya. Di sisi lain, jawaban ini mengatasi masalah asli yang membingungkan vektor inisialisasi untuk kunci. Itulah mengapa itu dipilih pada awalnya. Sekarang, posting ini telah menjadi lebih dari sekedar contoh kode tujuan, dan orang-orang di sini membuat beberapa contoh yang bagus - tepat di samping pertanyaan aslinya tentang apa.
GPI

3
@GPI Suara positif. "Contoh bagus" lainnya tidak terlalu bagus, dan mereka tidak benar-benar menjawab pertanyaan itu sama sekali. Alih-alih ini tampaknya menjadi tempat bagi pemula untuk menyalin sampel kriptografi secara membabi buta tanpa memahami bahwa mungkin ada masalah keamanan - dan, seperti biasa, ada.
Maarten Bodewes

17

IV yang Anda gunakan untuk dekripsi salah. Ganti kode ini

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

Dengan kode ini

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

Dan itu seharusnya menyelesaikan masalah Anda.


Di bawah ini termasuk contoh kelas AES sederhana di Java. Saya tidak menyarankan penggunaan kelas ini di lingkungan produksi, karena mungkin tidak memperhitungkan semua kebutuhan khusus aplikasi Anda.

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

Perhatikan bahwa AES tidak ada hubungannya dengan pengkodean, itulah sebabnya saya memilih untuk menanganinya secara terpisah dan tanpa perlu pustaka pihak ketiga.


Pertama, Anda belum menjawab pertanyaan awal. Kedua, mengapa Anda menjawab pertanyaan yang sudah dijawab dan diterima dengan baik? Saya pikir perlindungan seharusnya menghentikan spam ini.
TedTrippin

14
Seperti jawaban yang diterima, saya memilih untuk menjawab pertanyaan Anda melalui contoh. Saya memberikan potongan kode yang berfungsi penuh, yang menunjukkan kepada Anda cara menangani vektor inisialisasi dengan benar, antara lain. Adapun pertanyaan kedua Anda, saya merasa bahwa jawaban yang diperbarui diperlukan karena codec Apache tidak lagi diperlukan. Jadi ini bukan spam. Hentikan trippin.
k170

7
Sebuah IV memiliki tujuan khusus yaitu untuk mengacak ciphertext dan memberikan keamanan semantik. Jika Anda menggunakan pasangan kunci + IV yang sama, maka penyerang dapat menentukan apakah Anda telah mengirim pesan dengan awalan yang sama seperti sebelumnya. IV tidak harus rahasia, tetapi harus tidak dapat diprediksi. Cara yang umum adalah dengan mengawali IV ke ciphertext dan memotongnya sebelum dekripsi.
Artjom B.

4
downvote: hardcode IV, lihat Artjom B. komentar di atas mengapa itu buruk
Murmel

1
Mode CTR harus dipasangkan dengan NoPadding. Modus CTR tentu tidak diperlukan bukan CBC (kecuali firman bantalan berlaku), tetapi jika CTR yang digunakan, kemudian gunakan "/NoPadding". CTR adalah mode yang mengubah AES menjadi stream cipher, dan stream cipher beroperasi pada byte, bukan blok.
Maarten Bodewes

16

Dalam jawaban ini saya memilih untuk mendekati tema utama "Contoh enkripsi / dekripsi AES Java Sederhana" dan bukan pertanyaan debugging spesifik karena menurut saya ini akan menguntungkan sebagian besar pembaca.

Ini adalah ringkasan sederhana dari posting blog saya tentang enkripsi AES di Java, jadi saya sarankan untuk membacanya sebelum menerapkan apa pun. Namun saya masih akan memberikan contoh sederhana untuk digunakan dan memberikan beberapa petunjuk apa yang harus diperhatikan.

Dalam contoh ini saya akan memilih untuk menggunakan enkripsi terotentikasi dengan Galois / Counter Mode atau GCM mode. Alasannya adalah bahwa dalam banyak kasus Anda menginginkan integritas dan keaslian yang dikombinasikan dengan kerahasiaan (baca lebih lanjut di blog ).

Tutorial Enkripsi / Dekripsi AES-GCM

Berikut adalah langkah-langkah yang diperlukan untuk mengenkripsi / mendekripsi dengan AES-GCM dengan Java Cryptography Architecture (JCA) . Jangan gabungkan dengan contoh lain , karena perbedaan kecil dapat membuat kode Anda benar-benar tidak aman.

1. Buat Kunci

Karena itu tergantung pada kasus penggunaan Anda, saya akan menganggap kasus paling sederhana: kunci rahasia acak.

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

Penting:

2. Buat Vektor Inisialisasi

Sebuah vektor inisialisasi (IV) digunakan sehingga kunci rahasia yang sama akan menciptakan berbagai teks cipher .

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

Penting:

3. Enkripsi dengan IV dan Key

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

Penting:

  • menggunakan tag otentikasi 16 byte / 128 bit (digunakan untuk memverifikasi integritas / keaslian)
  • tag otentikasi akan secara otomatis ditambahkan ke teks sandi (dalam implementasi JCA)
  • karena GCM berperilaku seperti stream cipher, tidak diperlukan padding
  • digunakan CipherInputStreamsaat mengenkripsi potongan data yang besar
  • ingin data tambahan (non-rahasia) diperiksa jika diubah? Anda mungkin ingin menggunakan data terkait dengan cipher.updateAAD(associatedData); Lainnya di sini.

3. Menyambung ke Pesan Tunggal

Cukup tambahkan IV dan ciphertext. Seperti disebutkan di atas, infus tidak perlu dirahasiakan.

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

Secara opsional, enkode dengan Base64 jika Anda memerlukan representasi string. Gunakan implementasi bawaan Android atau Java 8 (jangan gunakan Apache Commons Codec - ini implementasi yang buruk). Encoding digunakan untuk "mengubah" array byte menjadi representasi string agar ASCII aman, misalnya:

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

4. Siapkan Dekripsi: Deserialisasi

Jika Anda telah mengkodekan pesan, pertama-tama decode ke dalam byte array:

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

Penting:

5. Dekripsi

Inisialisasi cipher dan setel parameter yang sama seperti dengan enkripsi:

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

Penting:

  • jangan lupa untuk menambahkan data yang terkait dengan cipher.updateAAD(associatedData);jika Anda menambahkan selama enkripsi.

Cuplikan kode yang berfungsi dapat ditemukan di inti ini.


Perhatikan bahwa implementasi Android (SDK 21+) dan Java (7+) terbaru harus memiliki AES-GCM. Versi lama mungkin kekurangannya. Saya tetap memilih mode ini, karena lebih mudah diimplementasikan selain lebih efisien dibandingkan dengan mode serupa dari Encrypt-then-Mac (misalnya AES-CBC + HMAC ). Lihat artikel ini tentang cara menerapkan AES-CBC dengan HMAC .


Masalahnya adalah bahwa meminta contoh secara eksplisit keluar dari topik SO. Dan masalah yang lebih besar adalah bahwa ini adalah potongan kode yang belum ditinjau, yang sulit divalidasi. Saya menghargai usaha tersebut, tetapi saya tidak berpikir bahwa SO harus menjadi tempat untuk ini.
Maarten Bodewes

1
Saya mengagumi upaya tersebut, jadi saya hanya akan menunjukkan satu kesalahan: "iv harus tidak dapat diprediksi dalam kombinasi dengan menjadi unik (mis. Gunakan iv acak)" - ini berlaku untuk mode CBC tetapi tidak untuk GCM.
Maarten Bodewes

this is true for CBC mode but not for GCMmaksud Anda seluruh bagian, atau hanya itu yang tidak benar-benar perlu tidak terduga?
Patrick Favre

1
"Jika Anda tidak mendapatkan topiknya maka Anda mungkin sebaiknya tidak menggunakan primitif tingkat rendah di tempat pertama" yakin, SEHARUSNYA, banyak pengembang masih melakukannya. Saya tidak yakin untuk menahan diri dari memasang konten berkualitas tinggi mengenai keamanan / kriptografi di tempat-tempat di mana seringkali tidak banyak solusi yang tepat untuk ini. - terima kasih telah menunjukkan kesalahan saya btw
Patrick Favre

1
OK, hanya karena saya suka jawaban wrt isinya (bukan tujuan): penanganan IV dapat disederhanakan terutama selama dekripsi: Java memudahkan untuk membuat IV langsung dari array byte yang ada. Hal yang sama berlaku untuk dekripsi, yang tidak harus dimulai dari offset 0. Semua penyalinan ini tidak perlu. Juga jika Anda harus mengirim panjang untuk IV (apakah Anda?) Lalu mengapa tidak menggunakan satu byte (unsigned) - Anda tidak akan melewati 255 byte untuk IV, bukan?
Maarten Bodewes

2

Versi Editor Online Runnable: -

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

Keren, senang membantu!
Bhupesh Pant

Kata sandi bukanlah kunci, IV tidak boleh statis. Kode masih diketik ketat, yang membuatnya tidak mungkin untuk merusak kunci. Tidak ada indikasi apa yang harus dilakukan dengan IV, atau gagasan bahwa itu harus tidak dapat diprediksi.
Maarten Bodewes

1

Seringkali merupakan ide yang baik untuk mengandalkan solusi yang disediakan pustaka standar:

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

Ini mencetak "Teks untuk dikodekan".

Solusi didasarkan pada Panduan Referensi Arsitektur Kriptografi Java dan https://stackoverflow.com/a/20591539/146745 jawaban.


5
Jangan pernah menggunakan mode ECB. Titik.
Konstantino Sparakis

1
ECB tidak boleh digunakan jika mengenkripsi lebih dari satu blok data dengan kunci yang sama, jadi untuk "Text to encode" itu sudah cukup baik. stackoverflow.com/a/1220869/146745
andrej

Kunci @AndroidDev dihasilkan di bagian kunci persiapan: aesKey = keygen.generateKey ()
andrej

1

Ini merupakan peningkatan dari jawaban yang diterima.

Perubahan:

(1) Menggunakan IV acak dan menambahkannya ke teks terenkripsi

(2) Menggunakan SHA-256 untuk menghasilkan kunci dari frasa sandi

(3) Tidak ada ketergantungan pada 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);
}

Sebuah hash masih bukan fungsi pembuatan kunci berbasis kata sandi / PBKDF. Entah Anda menggunakan kunci acak atau Anda menggunakan PBKDF seperti PBKDF2 / Enkripsi Berbasis Kata Sandi.
Maarten Bodewes

@MaartenBodewes Bisakah Anda menyarankan perbaikan?
wvdz

PBKDF2 hadir di Jawa, jadi saya rasa saya hanya menyarankan satu. Oke, saya tidak membuat kode satu, tapi itu meminta terlalu banyak menurut saya. Ada banyak contoh Enkripsi Berbasis Kata Sandi.
Maarten Bodewes

@MaartenBodewes Saya pikir ini mungkin perbaikan sederhana. Karena penasaran, kerentanan spesifik apa yang akan muncul jika menggunakan kode ini sebagaimana adanya?
wvdz

0

Solusi lain menggunakan java.util.Base64 dengan Spring Boot

Kelas Encryptor

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

Kelas 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

Contoh

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

2h41HH8Shzc4BRU3hVDOXA ==

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

jmendoza


-1

Versi yang dioptimalkan dari jawaban yang diterima.

  • tidak ada libs pihak ketiga

  • memasukkan IV ke dalam pesan terenkripsi (bisa publik)

  • panjang sandi bisa berapa saja

Kode:

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

Pemakaian:

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

Contoh keluaran:

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

Fungsi derivasi kata sandi Anda tidak aman. Saya tidak berharap e.printStackTrace()dalam apa yang disebut kode yang dioptimalkan.
Maarten Bodewes
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.