逆向华为路由器RSA加密算法并实现Java/Kotlin版本
华为路由器Web API RSA加密算法逆向与Java实现方案
问题背景
尝试通过Web API控制华为路由器时,发现部分数据采用RSA加密。已获取公钥,但测试多种标准RSA加密模式后,均无法得到与路由器前端加密一致的结果。
前端加密代码
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) { 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) { 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) { x[0] = 0; while (x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } 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; } 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"); } function RSADoPublic(x) { return x.modPowInt(this.e, this.n); } 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; } function RSAEncryptB64(text) { var h = this.encrypt(text); if (h) return hex2b64(h); else return null; } RSAKey.prototype.setPublic = RSASetPublic; RSAKey.prototype.doPublic = RSADoPublic; 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 + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc3) + _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; }
已知参数
- 十六进制模数:
d5eeead43ba5133e06cce6703b713db54331141d2707b8701a532173904b4e3bfca4bf73cdb7c56a640319299a083c780fa39d0fdc50aca6e0ea5d39c605cf90b88b33ed71126eea437fcd383576b11276df99425807e4c43bde60fcef38a11a6cbfb327377240b42dcf9e3d3abc1f37e62ca7efebfa247e879adeea9a395ed889916e91fd83199539dd9063f6fc306b106245b630f13ffea18eae7a486316b2c27b551214fd202993581276dfc407047f3f2da3e44161590b4cf5e12eab81633396b0eb17e487b3a12dd4a2a87726b487309801e0984ca222706127018e917ddbad3e9d9a7107e3cb54a9dad49ae7ba77252547758c2a3cf8c2c56e17b13591 - 十六进制指数:
010001 - 明文:
test123test - 路由器加密结果:
59a5a4fe4056612c9892ea2d5108c1e348b2fb70a85a1f0c7b112bfddf058f969e8fc5e797875f63b75eb59160c1df77c7d23dbe481905226d2001f32b4eee59ec795bd113c3606096f8cbdab8ada6d6df00f1621635b7dec60ba1814208a39a3aba2ea527bda2ea522f6b65273525e7fe03478a154904debb11b7523c50432cda45a3638a6b65f768acbcc3ef1ea5c11235e39343042ea570bf220bbad05973e5c6d58af3e64c0ad183b946252c801567fa11029bdc667d2144413a5f943813ef8daf2318079204ec615e68a871e29556cd43495e3ea0a84adbe6b2dbd7e4a8d3be78bdf64a61db1a8c1dd9683499ce9241dbd6ea5bee0325944d01a17d5199
已测试的标准模式(均不匹配)
RSA/ECB/OAEPWITHMD5ANDMGF1PADDINGRSA/ECB/OAEPWITHSHA1ANDMGF1PADDINGRSA/ECB/OAEPWITHSHA-256ANDMGF1PADDINGRSA/ECB/OAEPWITHSHA-384ANDMGF1PADDINGRSA/ECB/OAEPWITHSHA-512ANDMGF1PADDINGRSA/ECB/PKCS1Padding
前端加密流程分析
仔细拆解JS代码,核心加密步骤如下:
- 明文预处理:先对明文执行UTF-8编码,再进行标准Base64编码(字符集为
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=)。 - 分段加密:将Base64编码后的字符串按245字节分段(2048位RSA的PKCS#1 v1.5加密需预留11字节填充位,因此每段明文最大长度为256-11=245字节)。
- PKCS#1 v1.5随机填充:每段数据通过
pkcs1pad2函数执行PKCS#1 v1.5 Type 2填充,填充后长度固定为256字节。 - RSA加密与结果拼接:对每段填充后的数据执行RSA公钥加密,将加密结果转为十六进制字符串并拼接。
- 重试逻辑:若最终拼接结果长度不是256的整数倍,重新执行整个加密流程(解决随机填充导致的偶发长度异常)。
Java实现方案
以下代码完全对齐前端加密逻辑,基于BouncyCastle库实现:
依赖引入(Maven)
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
Java代码实现
import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.PublicKey; import java.security.Security; import java.security.spec.RSAPublicKeySpec; import java.math.BigInteger; import java.util.Base64; public class HuaweiRSAEncryptor { static { Security.addProvider(new BouncyCastleProvider()); } private static final String RSA_MODE = "RSA/ECB/PKCS1Padding"; private static final int SEGMENT_LENGTH = 245; private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); public static String encrypt(String plainText, String modulusHex, String exponentHex) throws Exception { // 加载RSA公钥 BigInteger modulus = new BigInteger(modulusHex, 16); BigInteger exponent = new BigInteger(exponentHex, 16); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(keySpec); // 明文预处理:UTF-8编码 -> Base64编码 byte[] utf8Bytes = plainText.getBytes("UTF-8"); String base64Str = BASE64_ENCODER.encodeToString(utf8Bytes); // 分段加密 StringBuilder resultBuilder = new StringBuilder(); int totalSegments = (int) Math.ceil((double) base64Str.length() / SEGMENT_LENGTH); Cipher cipher = Cipher.getInstance(RSA_MODE); cipher.init(Cipher.ENCRYPT_MODE, publicKey); for (int i = 0; i < totalSegments; i++) { int start = i * SEGMENT_LENGTH; int end = Math.min(start + SEGMENT_LENGTH, base64Str.length()); String segment = base64Str.substring(start, end); byte[] segmentBytes = segment.getBytes("UTF-8"); // 加密并转为十六进制字符串 byte[] encryptedBytes = cipher.doFinal(segmentBytes); resultBuilder.append(bytesToHex(encryptedBytes)); } // 模拟前端重试逻辑 String result = resultBuilder.toString(); if (result.length() % 512 != 0) { // 256字节对应512位十六进制字符 return encrypt(plainText, modulusHex, exponentHex); } return result; } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); } // 测试示例 public static void main(String[] args) throws Exception { String modulus = "d5eeead43ba5133e06cce6703b713db54331141d2707b8701a532173904b4e3bfca4bf73cdb7c56a640319299a083c780fa39d0fdc50aca6e0ea5d39c605cf90b88b33ed71126eea437fcd383576b11276df99425807e4c43bde60fcef38a11a6cbfb327377240b42dcf9e3d3abc1f37e
相关产品推荐
相关产品推荐

