
dorkbox.util.crypto.OpenSSLDecryptor.kt Maven / Gradle / Ivy
package dorkbox.util.crypto
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.*
import javax.crypto.BadPaddingException
import javax.crypto.Cipher
import javax.crypto.IllegalBlockSizeException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object OpenSSLDecryptor {
private val INDEX_KEY = 0
private val INDEX_IV = 1
private val ITERATIONS = 1
private val ARG_INDEX_FILENAME = 0
private val ARG_INDEX_PASSWORD = 1
private val SALT_OFFSET = 8
private val SALT_SIZE = 8
private val CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE
private val KEY_SIZE_BITS = 256
/**
* Thanks go to Ola Bini for releasing this source on his blog.
* The source was obtained from [here](http://olabini.com/blog/tag/evp_bytestokey/) .
*/
fun EVP_BytesToKey(key_len: Int, iv_len: Int, md: MessageDigest, salt: ByteArray?, data: ByteArray?, count: Int): Array {
val key = ByteArray(key_len)
var key_ix = 0
val iv = ByteArray(iv_len)
var iv_ix = 0
val both = arrayOf(key, iv)
if (data == null) {
return both
}
var md_buf: ByteArray? = null
var nkey = key_len
var niv = iv_len
var i = 0
var addmd = 0
while (true) {
md.reset()
if (addmd++ > 0) {
md.update(md_buf!!)
}
md.update(data)
if (null != salt) {
md.update(salt, 0, 8)
}
md_buf = md.digest()
i = 1
while (i < count) {
md.reset()
md.update(md_buf!!)
md_buf = md.digest()
i++
}
i = 0
if (nkey > 0) {
while (true) {
if (nkey == 0) {
break
}
if (i == md_buf!!.size) {
break
}
key[key_ix++] = md_buf[i]
nkey--
i++
}
}
if (niv > 0 && i != md_buf!!.size) {
while (true) {
if (niv == 0) {
break
}
if (i == md_buf.size) {
break
}
iv[iv_ix++] = md_buf[i]
niv--
i++
}
}
if (nkey == 0 && niv == 0) {
break
}
}
i = 0
while (i < md_buf!!.size) {
md_buf[i] = 0
i++
}
return both
}
@JvmStatic
fun main(args: Array) {
// /usr/bin/openssl enc -d -aes-256-cbc -md sha256 -in update_file.encrypted -out update_file.bin -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
// NON-PBKDF2, WITH BASE64
// openssl enc -aes-256-cbc -a -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
// openssl enc -aes-256-cbc -a -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
// NON-PBKDF2, WITHOUT BASE64
// openssl enc -aes-256-cbc -salt -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
// openssl enc -aes-256-cbc -d -md sha256 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
// PBKDF2 (NOT WORKING)
// openssl aes-256-cbc -a -salt -pbkdf2 -iter 1 -md sha256 -in password.txt -out password.txt.enc -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
// openssl aes-256-cbc -d -a -md sha256 -pbkdf2 -iter 1 -in password.txt.enc -out password.txt.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01 && cat password.txt.new
val decrypt = false
try {
val password = "xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01"
val passwordBytes = password.toByteArray(Charsets.US_ASCII)
// openssl enc -aes-256-cbc -d -md sha256 -in netref_install_2019.1.bin.enc -out netref_install_2019.1.bin.new -pass pass:xyfjWNl6yPIZfRYLu64L2sleiF8vD5xgHsJ3sa3Ya6sY01
// val plaintextFileName = "build/netref_install_2019.1.bin"
// val encryptedFileName = "build/netref_install_2019.1.bin.enc"
// val fileOutputStream = FileOutputStream(File(encryptedFileName))
// OpenSSLPBEOutputStream(fileOutputStream, password).use { outputStream ->
// Files.copy(Path.of(plaintextFileName), outputStream)
// }
//
// if (true) {
// return
// }
val plaintextFileName = "NetRefCommon/password.txt"
val encryptedFileName = "NetRefCommon/password.txt.enc"
if (decrypt) {
// --- read base 64 encoded file ---
val encryptedFile = File(encryptedFileName).absoluteFile
// this is WITH BASE64 (with openssl -a)
// val lines = Files.readAllLines(encryptedFile.toPath(), Charsets.US_ASCII)
// val sb = StringBuilder()
// for (line in lines) {
// sb.append(line.trim())
// }
// val dataBase64 = sb.toString()
// val headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64) // when base64 encoded
// this is NOT base64
val headerSaltAndCipherText = encryptedFile.readBytes()
// --- extract salt & encrypted ---
// header is "Salted__", ASCII encoded, if salt is being used (the default)
val salt = Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
val encrypted = Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.size)
// --- specify cipher and digest for EVP_BytesToKey method ---
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
val md = MessageDigest.getInstance("SHA-256")
// --- create key and IV ---
// the IV is useless, OpenSSL might as well have use zero's
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt, passString.toByteArray(ASCII), ITERATIONS)
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
/////////////////////
// val openssl = OpenSSLPBEParametersGenerator()
// openssl.init(passString.toByteArray(Charsets.US_ASCII), salt)
// val keyAndIV = openssl.generateDerivedParameters(256)
//
// val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
// val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
//
// val key = SecretKeySpec(keyBytes, "AES")
// val iv = IvParameterSpec(ivBytes)
//////////////////////
var hash = ByteArray(0)
var keyAndIV = ByteArray(0)
md.update(passwordBytes)
md.update(salt)
hash = md.digest()
keyAndIV = hash.clone()
// 1 round
md.update(hash)
md.update(passwordBytes)
md.update(salt)
hash = md.digest()
keyAndIV = concat(keyAndIV, hash)
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
val key = SecretKeySpec(keyBytes, "AES")
val iv = IvParameterSpec(ivBytes)
///////////////////
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
// val tmp = factory.generateSecret(spec)
// val key = SecretKeySpec(tmp.encoded, "AES")
//
//
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
// val iv = IvParameterSpec(ivBytes)
/////////////////////////
// --- initialize cipher instance and decrypt ---
aesCBC.init(Cipher.DECRYPT_MODE, key, iv)
val decrypted = aesCBC.doFinal(encrypted)
val answer = String(decrypted, Charsets.UTF_8)
println(answer)
}
else {
// read plaintext file
val plainTextFile = File(plaintextFileName).absoluteFile
val data= plainTextFile.readBytes()
// --- create salt ---
val salt = ByteArray(8)
val secureRandom = SecureRandom()
secureRandom.nextBytes(salt)
// --- specify cipher and digest for EVP_BytesToKey method ---
val aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding")
val md = MessageDigest.getInstance("SHA-256")
//////////////////////////////////////////
// the IV is useless, OpenSSL might as well have use zero's
// val keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / java.lang.Byte.SIZE, aesCBC.blockSize, md, salt,
// passString.toByteArray(Charsets.US_ASCII), ITERATIONS)
// val key = SecretKeySpec(keyAndIV[INDEX_KEY], "AES")
// val iv = IvParameterSpec(keyAndIV[INDEX_IV])
//////////////////////////////////////////
var hash = ByteArray(0)
var keyAndIV = ByteArray(0)
md.update(passwordBytes)
md.update(salt)
hash = md.digest()
keyAndIV = hash.clone()
// 1 round
md.update(hash)
md.update(passwordBytes)
md.update(salt)
hash = md.digest()
keyAndIV = concat(keyAndIV, hash)
val keyBytes = Arrays.copyOfRange(keyAndIV, 0, 32)
val ivBytes = Arrays.copyOfRange(keyAndIV, 32, 48)
val key = SecretKeySpec(keyBytes, "AES")
val iv = IvParameterSpec(ivBytes)
//////////////////////////////////////////
// val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
// val spec = PBEKeySpec(passString.toCharArray(), salt, ITERATIONS, KEY_SIZE_BITS)
// val tmp = factory.generateSecret(spec)
// val key = SecretKeySpec(tmp.encoded, "AES")
//
//
// val ivBytes = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
// val iv = IvParameterSpec(ivBytes)
/////////////////////////
// --- initialize cipher instance and encrypt ---
aesCBC.init(Cipher.ENCRYPT_MODE, key, iv)
val encrypted = aesCBC.doFinal(data)
// val cipher = OpenSSLPBECommon.initializeCipher(password, salt, Cipher.ENCRYPT_MODE)
// val encrypted = cipher.doFinal(data)
// "Salted__" + salt + encrypted
val a1 = "Salted__".toByteArray(Charsets.US_ASCII)
val finalEncrypted = concat(concat(a1, salt), encrypted)
val encryptedFile = File(encryptedFileName).absoluteFile
Files.deleteIfExists(encryptedFile.toPath())
// WITH BASE64 By default the encoded file has a line break every 64 characters
// encryptedFile.writeBytes(Base64.getMimeEncoder(64, "\n".toByteArray(Charsets.US_ASCII)).encode(finalEncrypted))
// No base64
encryptedFile.writeBytes(finalEncrypted)
}
}
catch (e: BadPaddingException) {
// AKA "something went wrong"
throw IllegalStateException("Bad password, algorithm, mode or padding;" + " no salt, wrong number of iterations or corrupted ciphertext.")
}
catch (e: IllegalBlockSizeException) {
throw IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.")
}
catch (e: GeneralSecurityException) {
throw IllegalStateException(e)
}
catch (e: IOException) {
throw IllegalStateException(e)
}
}
fun concat(a: ByteArray, b: ByteArray): ByteArray {
val c = ByteArray(a.size + b.size)
System.arraycopy(a, 0, c, 0, a.size)
System.arraycopy(b, 0, c, a.size, b.size)
return c
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy