Since the website of my school is too difficult to use, I decided to write a mobile app to have a better user expreience last year. I have used Jsoup and other utilities to made it.
I found it's much more difficult to use recently, and there are more autherization pages in the website. I spent a whole day to find out how to translate the code from JS to Kotlin.
Is there a way that I can get the ciphertext of the values or How to make the code to be runnable in the Rhino JavaScript evaluator.
The encryptBase64
code is like this, I don't know how to manage to get the ciphertext
in Kotlin
encryptBase64: function (e) {//e=password
var r = f();
var t = s.default.MD5(r);
console.log("r="+r);//r=GN12MVOFWVZJ51KZ
console.log("t="+t);//r=GN12MVOFWVZJ51KZ
console.log("l="+l);//l=c2df1fd689074ff84521cea43a677bbf
var o = s.default.AES.encrypt(r, l, {iv: l, mode: a, padding: d});
var n = s.default.AES.encrypt(e, t, {iv: t, mode: a, padding: d});
console.log("o="+o)//o=tAzMY2LKjgC96ZJHXYjorazfL56s//vgFgoerDmaH3g=
console.log("n="+n)//n=GQNoFJVGL5qdLyb7KxFXAA==
var o_cipher=o.ciphertext
var n_cipher=n.ciphertext
console.log("o_cipher="+o_cipher)o_cipher=b40ccc6362ca8e00bde992475d88e8adacdf2f9eacfffbe0160a1eac399a1f78
console.log("n_cipher="+n_cipher)//n_cipher=1903681495462f9a9d2f26fb2b115700
var i = s.default.lib.WordArray.create([].concat(c(o_cipher.words), c(n_cipher.words)));
console.log("i="+i)//i=b40ccc6362ca8e00bde992475d88e8adacdf2f9eacfffbe0160a1eac399a1f781903681495462f9a9d2f26fb2b115700
var stringifiedI=s.default.enc.Base64.stringify(i)
console.log("stringified i="+stringifiedI)//stringified i=tAzMY2LKjgC96ZJHXYjorazfL56s//vgFgoerDmaH3gZA2gUlUYvmp0vJvsrEVcA
return stringifiedI
}
I thought the ciphertext
may be something like AES, not RSA nor MD5, because the server can decrypt it without key (I did not find anything else but the password encrypted).
Ask the school for help is impossible, my school does not approve it.
The Full code is as below (I think it's something like CryptoJS): Full Code in GitHub
I have created an aes encrypt/decrypt extension function like this: aes.kt
And a js.kt that contains callJs()
function.
I tried to use the callJs()
to get the result.
//fun main
val a= callJs(JS.encryptCode,"aesOene","encryptBase64","password")
println(a)
object JS{
val encryptCode: String by lazy {
val bytes = this::class.java.getResourceAsStream("/aesOene.js")!!.readBytes()
String(bytes)
}
}
And I got this
Exception in thread "main" org.mozilla.javascript.EcmaError: ReferenceError: "window" is not defined. (aesOene#1)
Than I tried to implement the encryptBase64()
with this:
// fun main
val b=encryptBase64("password")
println(b)
fun aesKeyBuilder():String{
val p= arrayListOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")
val builder=StringBuilder()
for (t in 0 until 16){
val o= ceil(35*Math.random()).toInt()
builder.append(p[o])
}
return builder.toString()
}
fun encryptBase64(e:String):String{
val r=aesKeyBuilder()
val l="ass-apex".md5
val t=r.md5
val o=r.encryptByAES(l,)
val n=e.encryptByAES(t,)
val i=o+n
return i.base64
}
And it does not get the correct result.
encryptBase64()
in Kotlin or JavaFor a correct execution, the h.encryptBase64()
function requires a WordArray l
, which is either set equal to the MD5 hash of the password e
using the function h.setSecret(e)
or which can alternatively be defined directly as any 16 bytes WordArray, e.g. l = CryptoJS.enc.Utf8.parse('0123456789012345')
.
The h.encryptBase64()
function then does the following:
f()
a random 16 character string r
is generated, which consists of a sequence of the characters from p
.r
the MD5 hash t
is generated.r
is encrypted with AES in CBC mode with PKCS#7 padding (ciphertext o_cipher
). l
is used for both the key and the IV.n_cipher
). t
is used for both the key and the IV.o_cipher
and n_cipher
are concatenated (result i
).i
is Base64 encoded and returned.Be aware that the current JavaScript code is unnecessarily complicated and riddled with vulnerabilities (e.g. MD5, Math.Random
).
The usual approach would use a reliable key derivation function (at least PBKDF2) in conjunction with a random, non-secret salt to generate a key that would be used to encrypt the plaintext.
The salt would be passed to the decrypting side together with the ciphertext so that the key could be reconstructed.
Actually, it would make sense to revise the JavaScript code before porting, but since you are probably interested in a 1:1 porting of the unchanged code, this is described below:
The aesKeyBuilder()
function you posted can in principle be used as a counterpart to f()
. However, Math.random()
should be replaced by SecureRandom#nextDouble()
, since Math.random()
is not cryptographically secure:
import java.security.SecureRandom
...
fun aesKeyBuilder(): String {
val p = arrayListOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z")
val builder = StringBuilder()
val secureRandom = SecureRandom()
for (t in 0 until 16){
val o= ceil(35 * secureRandom.nextDouble()).toInt()
builder.append(p[o])
}
return builder.toString()
}
The next step is to define a function that performs the encryption with AES in CBC mode and PKCS#7 padding, e.g.:
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
...
fun aesEncrypt(key: ByteArray, data: ByteArray): ByteArray {
val secretKeySpec = SecretKeySpec(key, "AES")
val ivParameterSpec = IvParameterSpec(key)
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
return cipher.doFinal(data)
}
With this, the logic of the JavaScript code can be ported to Kotlin as follows:
import android.util.Base64
import java.security.MessageDigest
...
val password = "my passphrase"
val plaintext = "The quick brown fox jumps over the lazy dog"
val key_l = MessageDigest.getInstance("MD5").digest(password.toByteArray())
val key_r = aesKeyBuilder().toByteArray()
val key_t = MessageDigest.getInstance("MD5").digest(key_r)
val encKey_o = aesEncrypt(key_l, key_r)
val ciphertext_n = aesEncrypt(key_t, plaintext.toByteArray())
val result_i = encKey_o + ciphertext_n
val resultB64 = Base64.encodeToString(result_i, Base64.NO_WRAP);
Test:
For a test, it is best to implement a JavaScript code for decryption that decrypts the ciphertext generated by the JavaScript code. The Kotlin code can be considered compatible if this JavaScript decryption code also decrypts the ciphertext generated by the Kotlin code.
The following JavaScript code successfully performs such a test:
var a = CryptoJS.mode.CBC,
d = CryptoJS.pad.Pkcs7, l = "", u = "",
p = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
f = function () {
return function (e) {
for (var r = "", t = 0; t < e; t++) {
var o = Math.ceil(35 * Math.random());
r += p[o]
}
return r
}(16)
},
h = {
encryptBase64: function (e) {
var r = f();
var t = CryptoJS.MD5(r);
console.log("r="+r);
console.log("t="+t);
console.log("l="+l);
var o = CryptoJS.AES.encrypt(r, l, {iv: l, mode: a, padding: d});
var n = CryptoJS.AES.encrypt(e, t, {iv: t, mode: a, padding: d});
console.log("o="+o)
console.log("n="+n)
var o_cipher=o.ciphertext
var n_cipher=n.ciphertext
console.log("o_cipher="+o_cipher)
console.log("n_cipher="+n_cipher)
var i = CryptoJS.lib.WordArray.create([].concat(c(o_cipher.words), c(n_cipher.words)));
console.log("i="+i)
var stringifiedI=CryptoJS.enc.Base64.stringify(i)
console.log("stringified i="+stringifiedI)
return stringifiedI
},
setSecret: function (e) {
u = e, l = CryptoJS.MD5(u)
}
};
function c(e) {
if (Array.isArray(e)) {
for (var r = 0, t = Array(e.length); r < e.length; r++) t[r] = e[r];
return t
}
return Array.from(e)
}
// encryption
h.setSecret('my passphrase')
var ciphertext = h.encryptBase64('The quick brown fox jumps over the lazy dog')
console.log('Ciphertext:', ciphertext)
// decryption from JavaScript ciphertext
var ciphertextWA = CryptoJS.enc.Base64.parse(ciphertext)
var encryptedKeyWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(0, 256 / 32))
var encryptedDataWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(256 / 32))
var l = CryptoJS.MD5('my passphrase') // apply passphrase from setSecret
var r = CryptoJS.AES.decrypt({ciphertext: encryptedKeyWA}, l, {iv: l, mode: a, padding: d})
var t = CryptoJS.MD5(r)
var e = CryptoJS.AES.decrypt({ciphertext: encryptedDataWA}, t, {iv: t, mode: a, padding: d})
console.log('Decrypted:', e.toString(CryptoJS.enc.Utf8))
// decryption from Kotlin ciphertext
var ciphertextFromKotlin = 'rFXxMQjsIhfCOPiIbgLTJJioVqb7ONCp9Tx3WoTpbF9QxlJNdpR4r6mPtBbnSsEl9PL30hoER6P+RJS9hVUbs/i7ipxhwqiFEbbH97ryZmM='
var ciphertextWA = CryptoJS.enc.Base64.parse(ciphertextFromKotlin)
var encryptedKeyWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(0, 256 / 32))
var encryptedDataWA = CryptoJS.lib.WordArray.create(ciphertextWA.words.slice(256 / 32))
var l = CryptoJS.MD5('my passphrase') // apply passphrase from Kotlin code
var r = CryptoJS.AES.decrypt({ciphertext: encryptedKeyWA}, l, {iv: l, mode: a, padding: d})
var t = CryptoJS.MD5(r)
var e = CryptoJS.AES.decrypt({ciphertext: encryptedDataWA}, t, {iv: t, mode: a, padding: d})
console.log('Decrypted:', e.toString(CryptoJS.enc.Utf8))
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>