I'm trying control my huawei router using its web api, but some things are RSA encrypted. I have the public key, but I am struggling to find the algorithm they use to encrypt things.
The code they use is below:
function doRSAEncrypt(encstring) {
if (encstring == '') {
return '';
}
if (typeof (g_moduleswitch.encrypt_enabled) == 'undefined' || g_moduleswitch.encrypt_enabled != 1) {
return encstring;
}
if (g_encPublickey.e == '') {
if (true == g_scarm_login) {
var pubkeyArray = getPubkey();
g_encPublickey.e = pubkeyArray[1];
g_encPublickey.n = pubkeyArray[0];
} else {
getEncpubkey();
}
}
var rsa = new RSAKey();
rsa.setPublic(g_encPublickey.n, g_encPublickey.e);
encstring = base64_encode(encstring);
var num = encstring.length / 245;
var restotal = '';
for (i = 0; i < num; i++) {
var encdata = encstring.substr(i * 245, 245);
var res = rsa.encrypt(encdata);
restotal += res;
}
if (restotal.length % 256 != 0) {
restotal = doRSAEncrypt(encstring);
}
return restotal;
}
function parseBigInt(str, r) {
return new BigInteger(str, r);
}
// PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint
function pkcs1pad2(s, n) {
if (n < s.length + 11) { // TODO: fix for utf-8 alert("Message too long for RSA");
return null;
}
var ba = new Array();
var i = s.length - 1;
while (i >= 0 && n > 0) {
var c = s.charCodeAt(i--);
if (c < 128) { // encode using utf-8
ba[--n] = c;
} else if ((c > 127) && (c < 2048)) {
ba[--n] = (c & 63) | 128;
ba[--n] = (c >> 6) | 192;
} else {
ba[--n] = (c & 63) | 128;
ba[--n] = ((c >> 6) & 63) | 128;
ba[--n] = (c >> 12) | 224;
}
}
ba[--n] = 0;
var rng = new SecureRandom();
var x = new Array();
while (n > 2) { // random non-zero pad
x[0] = 0;
while (x[0] == 0)
rng.nextBytes(x);
ba[--n] = x[0];
}
ba[--n] = 2;
ba[--n] = 0;
return new BigInteger(ba);
}
// "empty" RSA key constructor
function RSAKey() {
this.n = null;
this.e = 0;
this.d = null;
this.p = null;
this.q = null;
this.dmp1 = null;
this.dmq1 = null;
this.coeff = null;
}
// Set the public key fields N and e from hex strings
function RSASetPublic(N, E) {
if (N != null && E != null && N.length > 0 && E.length > 0) {
this.n = parseBigInt(N, 16);
this.e = parseInt(E, 16);
} else alert("Invalid RSA public key");
}
// Perform raw public operation on "x": return x^e (mod n)
function RSADoPublic(x) {
return x.modPowInt(this.e, this.n);
}
// Return the PKCS#1 RSA PKCS#1 RSA encryption of "text" as an even-length hex string
function RSAEncrypt(text) {
var m = pkcs1pad2(text, (this.n.bitLength() + 7) >> 3);
if (m == null)
return null;
var c = this.doPublic(m);
if (c == null)
return null;
var h = c.toString(16);
if ((h.length & 1) == 0)
return h;
else
return "0" + h;
}
// Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string
function RSAEncryptB64(text) {
var h = this.encrypt(text);
if (h)
return hex2b64(h);
else
return null;
}
// protectedRSAKey.prototype.doPublic = RSADoPublic;
// publicRSAKey.prototype.setPublic = RSASetPublic;
RSAKey.prototype.encrypt = RSAEncrypt;
RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
function base64_encode(input) {
_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;
input = _utf8_encode(input);
while (i < input.length) {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
}
return output;
}
function _utf8_encode(string) {
string = string.replace(/\r\n/g, "\n");
var utftext = "";
for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
For modulus d5eeead43ba5133e06cce6703b713db54331141d2707b8701a532173904b4e3bfca4bf73cdb7c56a640319299a083c780fa39d0fdc50aca6e0ea5d39c605cf90b88b33ed71126eea437fcd383576b11276df99425807e4c43bde60fcef38a11a6cbfb327377240b42dcf9e3d3abc1f37e62ca7efebfa247e879adeea9a395ed889916e91fd83199539dd9063f6fc306b106245b630f13ffea18eae7a486316b2c27b551214fd202993581276dfc407047f3f2da3e44161590b4cf5e12eab81633396b0eb17e487b3a12dd4a2a87726b487309801e0984ca222706127018e917ddbad3e9d9a7107e3cb54a9dad49ae7ba77252547758c2a3cf8c2c56e17b13591
and exponent 010001
(both in hex) the string test123test
yields 59a5a4fe4056612c9892ea2d5108c1e348b2fb70a85a1f0c7b112bfddf058f969e8fc5e797875f63b75eb59160c1df77c7d23dbe481905226d2001f32b4eee59ec795bd113c3606096f8cbdab8ada6d6df00f1621635b7dec60ba1814208a39a3aba2ea527bda2ea522f6b65273525e7fe03478a154904debb11b7523c50432cda45a3638a6b65f768acbcc3ef1ea5c11235e39343042ea570bf220bbad05973e5c6d58af3e64c0ad183b946252c801567fa11029bdc667d2144413a5f943813ef8daf2318079204ec615e68a871e29556cd43495e3ea0a84adbe6b2dbd7e4a8d3be78bdf64a61db1a8c1dd9683499ce9241dbd6ea5bee0325944d01a17d5199
using their encryption.
I tried various RSA modes from Bouncycastle in Java like
RSA/ECB/OAEPWITHMD5ANDMGF1PADDING
RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING
RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING
RSA/ECB/OAEPWITHSHA-384ANDMGF1PADDING
RSA/ECB/OAEPWITHSHA-512ANDMGF1PADDING
RSA/ECB/OAEPWithSHA-1AndMGF1Padding
RSA/ECB/PKCS1Padding
but none of them yielded the same result as the Huawei encrypt function did
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.SecureRandom
import java.util.*
import kotlin.math.ceil
import kotlin.math.min
class RSAEncrypt(modulo: String, exponent: String) {
private val modulo = BigInteger(modulo, 16)
private val exponent = BigInteger(exponent, 16)
fun doRSAEncrypt(encstring: String): String {
val base64String = Base64.getEncoder().encodeToString(encstring.toByteArray())
val num = base64String.length / 245.0
var restotal = ""
for (i in 0 until ceil(num).toInt()) {
val encryptedData = base64String.substring(i * 245, min(245, base64String.length))
val res = this.encrypt(encryptedData);
restotal += res;
}
if (restotal.length % 256 !== 0) {
restotal = doRSAEncrypt(base64String)
}
return restotal
}
private fun pkcs1pad2(s: String, n: Int): BigInteger? {
if (n < s.length + 11) { // TODO: fix for utf-8
return null
}
var n = n
val ba = MutableList(n) { 0 }
var i = s.length - 1;
while (i >= 0 && n > 0) {
val c = Character.codePointAt(s, i--)
if (c < 128) { // encode using utf-8
ba[--n] = c;
} else if ((c > 127) && (c < 2048)) {
ba[--n] = (c and 63) or 128;
ba[--n] = (c shr 6) or 192;
} else {
ba[--n] = (c and 63) or 128;
ba[--n] = ((c shr 6) and 63) or 128;
ba[--n] = (c shr 12) or 224;
}
}
ba[--n] = 0;
val rng = SecureRandom()
val x = ByteArray(n) { 0 }
while (n > 2) {
x[0] = 0;
while (x[0].toInt() == 0) {
rng.nextBytes(x)
}
ba[--n] = x[0].toInt()
}
ba[--n] = 2;
ba[--n] = 0;
val baos = ByteArrayOutputStream()
for (item in ba) {
if (item < -128 || item > 127) {
throw Exception("You code is shit")
}
baos.write(item)
}
return BigInteger(baos.toByteArray())
}
private fun doPublic(x: BigInteger): BigInteger {
return x.modPow(this.exponent, this.modulo)
}
private fun encrypt(text: String): String? {
val maxLength = (this.modulo.bitLength() + 7) shr 3
val m = pkcs1pad2(text, maxLength) ?: return null
val c = this.doPublic(m)
var h = c.toString(16);
val length = h.length;
// fix zero before result
for (i in 0 until maxLength * 2 - length) {
h = "0$h";
}
return h
}
}
Implemented in Kotlin