You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

逆向华为路由器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/OAEPWITHMD5ANDMGF1PADDING
  • RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING
  • RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING
  • RSA/ECB/OAEPWITHSHA-384ANDMGF1PADDING
  • RSA/ECB/OAEPWITHSHA-512ANDMGF1PADDING
  • RSA/ECB/PKCS1Padding

前端加密流程分析

仔细拆解JS代码,核心加密步骤如下:

  1. 明文预处理:先对明文执行UTF-8编码,再进行标准Base64编码(字符集为ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=)。
  2. 分段加密:将Base64编码后的字符串按245字节分段(2048位RSA的PKCS#1 v1.5加密需预留11字节填充位,因此每段明文最大长度为256-11=245字节)。
  3. PKCS#1 v1.5随机填充:每段数据通过pkcs1pad2函数执行PKCS#1 v1.5 Type 2填充,填充后长度固定为256字节。
  4. RSA加密与结果拼接:对每段填充后的数据执行RSA公钥加密,将加密结果转为十六进制字符串并拼接。
  5. 重试逻辑:若最终拼接结果长度不是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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 00:05:46