Jadi saya memiliki perintah openssl yang sangat mendasar yang diberikan kepada saya openssl smime -encrypt -binary -aes-256-cbc -in $inPath -out $encryptedPath -outform DER $pubCert, perintah ini juga berfungsi dengan benar dan mengeluarkan file terenkripsi. Saya perlu menggunakan yang setara dengan perintah ini dalam aplikasi Java, lebih disukai tanpa menjalankan proses dan menggunakan openssl itu sendiri (hanya karena saya merasa itu mungkin praktik yang buruk).

Saya telah meneliti cukup banyak dan sepertinya tidak ada yang setara di luar sana yang dapat saya temukan .. Saya telah mencoba beberapa hal dan kebanyakan dari mereka sepertinya tidak berhasil. Yang aneh adalah... Saya bisa mendapatkan string "Hello World" sederhana untuk dienkripsi menggunakan kode yang saya tulis (walaupun saya tidak percaya itu mengenkripsinya dengan benar karena sandi saya disetel ke "RSA" bukan " AES") tetapi ketika array byte berasal dari file, itu gagal secara diam-diam dan hanya menulis 0 byte. Sekarang seperti inilah tampilan kode saya.

  Cipher aes = Cipher.getInstance("RSA");
  CertificateFactory certF = CertificateFactory.getInstance("X.509");
  File public_cert = new File( getClass().getClassLoader().getResource("public.crt").getFile());
  FileInputStream certIS = new FileInputStream(public_cert);
  X509Certificate cert = (X509Certificate) certF.generateCertificate(certIS);
  certIS.close();
  aes.init(Cipher.ENCRYPT_MODE, cert);

  File tarGz = new File("C:\\volatile\\generic.tar.gz");
  FileInputStream fis = new FileInputStream(tarGz);
  byte[] tarGzBytes = FileUtils.readFileToByteArray(tarGz);
  tarGzBytes = "Hello World".getBytes();
  ByteArrayInputStream bais = new ByteArrayInputStream("Hello World".getBytes());
  File encFile = new File("C:\\volatile\\generic.tar.gz.enc");
  FileOutputStream enc = new FileOutputStream(encFile);

  CipherOutputStream cos = new CipherOutputStream(enc, aes);
  cos.write(tarGzBytes);
  //IOUtils.copy(fis, cos);
  //IOUtils.copy(bais, cos);
  cos.flush();
  cos.close();

Jadi ini berfungsi, dan mengenkripsi file kecil dengan Hello World terenkripsi di dalamnya. Saya tidak percaya ini adalah AES-256-CBC, dan itu tidak berfungsi ketika saya menggunakan FileUtils.readFileToByteArray(tarGz), meskipun array byte yang dihasilkan dalam debugger berukuran sekitar 94MB dengan benar. Yang tampaknya sangat aneh bagi saya, itu bekerja dengan "Hello World".toByteArray() dan bukan FileUtils.readAllBytes(tarGz). Sebagai catatan tambahan, ByteArrayInputStream menggunakan IOUtils.copy berfungsi, sedangkan versi FileInputStream juga menulis 0 byte.

Juga, ketika saya mengatur mode sandi ke AES/CBC/PKCS5Padding (karena saya menemukan sesuatu online yang menyarankan untuk mengaturnya dan itu terlihat lebih seperti yang saya inginkan) saya mendapatkan pesan kesalahan berikut:

java.security.InvalidKeyException: No installed provider supports this key: sun.security.rsa.RSAPublicKeyImpl
at javax.crypto.Cipher.chooseProvider(Cipher.java:892)
at javax.crypto.Cipher.init(Cipher.java:1724)
~~~~

Jika ada yang punya saran, atau jika saya perlu memberikan informasi lebih lanjut, beri tahu saya. Saya cukup macet sekarang dan saya pada titik ini berdebat menulis skrip untuk menjalankan perintah openssl dan menjalankan skrip itu dari Java ...

Kesimpulan

Setelah membaca jawaban @ dave-thompson-085, saya menyadari bahwa ada alasan yang sangat bagus mengapa saya tidak dapat menemukan apa yang ingin saya lakukan. Jadi karena itu saya memutuskan untuk melanjutkan dan memanggil proses openssl dari Java menggunakan pembuat proses. Saya dapat membuat ulang perintah openssl dari atas sebagai Proses di Java, mulai dan jalankan dengan kode berikut:

  File cert = new File(getClass().getClassLoader().getResource("public.crt").getFile());
  ProcessBuilder openSslBuilder = new ProcessBuilder("openssl", "smime", "-encrypt", "-binary",
      "-aes-256-cbc", "-in", "C:\\volatile\\generic.tar.gz", "-out",
      "C:\\volatile\\generic.tar.gz.enc", "-outform", "DER", cert.getPath());
  Process openssl = openSslBuilder.start();

  openssl.waitFor();
  System.out.println(openssl.exitValue());
  openssl.destroy();

Semoga ini membantu orang lain yang ingin mencoba ini juga dan mungkin menghemat banyak waktu seseorang!

1
Brandon Stout 16 Maret 2019, 18:30

1 menjawab

Jawaban Terbaik

Pertama, untuk memperjelas: perintah openssl smime sebenarnya menangani format S/MIME dan CMS (alias PKCS7); ini terkait tetapi standar berbeda yang pada dasarnya menggunakan format file yang berbeda untuk operasi kriptografi yang pada dasarnya sama. Dengan -outform DER Anda sebenarnya melakukan CMS/PKCS7.

Kedua dan yang lebih mendasar: CMS/PKCS7, dan S/MIME, dan sebagian besar skema kriptografi umum lainnya seperti PGP, sebenarnya enkripsi hibrida. Data Anda sebenarnya tidak dienkripsi dengan RSA; sebagai gantinya data Anda dienkripsi dengan algoritme simetris (di sini AES-256-CBC, karena Anda memilihnya) menggunakan kunci yang dibuat secara acak yang disebut DEK (kunci enkripsi data) dan DEK dienkripsi dengan RSA menggunakan kunci publik penerima (diperoleh dari sertifikat mereka), dan kedua hasil tersebut ditambah banyak metadata diatur ke dalam struktur data yang cukup rumit. Penerima dapat mengurai pesan untuk mengekstrak potongan-potongan ini, kemudian menggunakan RSA dengan kunci pribadi mereka untuk mendekripsi DEK, kemudian AES-mendekripsi data dengan DEK. Perhatikan bahwa Anda selalu menggunakan kunci RSA untuk RSA, dan kunci AES untuk AES; kunci simetris hampir semuanya hanya bit dan hanya bervariasi dalam ukuran, tetapi kunci kriptografi kunci publik termasuk RSA (juga DH, DSA, ECC, dan lainnya) jauh lebih rumit dan tidak dapat dicampur.

Mencoba mengenkripsi data secara langsung dengan RSA seperti yang Anda lakukan, selain salah, secara umum tidak akan berhasil karena RSA hanya dapat mengenkripsi data dalam jumlah terbatas, tergantung pada ukuran kunci yang digunakan, biasanya sekitar 100-200 byte. Enkripsi simetris juga memiliki beberapa batasan, tetapi umumnya jauh lebih besar; AES-CBC bagus untuk sekitar 250,000,000,000,000,000 byte.

Jika Anda ingin menerapkannya sendiri, Anda perlu membaca standar untuk CMSkhususnya bagian di EnvelopedData menggunakan KeyTransRecipientInfo (untuk RSA), dikombinasikan dengan aturan untuk ASN.1 BER/DER encoding. Ini bukan pekerjaan sederhana, meskipun bisa dilakukan jika Anda mau berusaha.

Jika Anda dapat menggunakan perpustakaan pihak ketiga di Java, toples 'bcpkix' dari https://www.bouncycastle.org< /a> memiliki rutinitas yang mendukung CMS, antara lain. Ini biasanya mudah jika Anda menulis program untuk dijalankan sendiri, atau di departemen Anda. Jika ini akan dikirimkan ke pengguna luar atau pelanggan yang mungkin tidak suka harus mengelola ketergantungan, mungkin tidak.

Yang mengatakan, menjalankan program lain untuk melakukan sesuatu tidak selalu merupakan praktik yang buruk di buku saya, dan dapat dilakukan langsung dari Java (tanpa skrip). Kecuali Anda (perlu) melakukannya sangat sering, misalnya 100 kali per detik.

4
dave_thompson_085 16 Maret 2019, 16:26