I’ve had this B311As-853 router for almost 2 years now. I’ve been using it as an emergency network for my private cloud. It has a China Unicom SIM card inserted, so when there is a power outage at home, the router switches the network connection to this 4G router to maintain communication between my personal server and private cloud, ensuring accessibility at all times. This router provides SMS sending service, but it’s inconvenient to log in each time. It would be great if we could send SMS through an API.(English version Translated by GPT-3.5, 返回中文 )
Introduction After several attempts, I found that the router can only be logged in through a connected device. If I try to access the router management address through Nginx reverse proxy and request Nginx’s address, the token retrieval will fail (I don’t have time to tinker with it). So, if you want to provide SMS service, you need a device connected to the router, like a Raspberry Pi or similar. I’m using the B311As-853 router shown below, along with its management interface.
Approach The approach is quite simple. I’ve written an article before on Logging in to Huawei WS5200 Router using Java , and the approach is similar. The task is to find the SMS sending API, simulate logging in to the router, and simulate sending SMS. The only difference is that this B311As-853 router uses XML for login and requests. However, it is recommended to use this feature for sending SMS only to yourself or your friends. It’s better not to send to many people to avoid complaints and harassment, as others can see your number.
Let’s start by inspecting the login process with F12
First, access /html/index.html
. The content inside is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html id ="html" > <head > <head > <meta name ="csrf_token" content ="jIe0fYkd06vlXIaEXfIboaFlAJ2TZemf" > <meta name ="csrf_token" content ="fHQRMzwE5D0UoOYSDo7F3lHtlJ2vxxoi" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" /> <meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" /> <meta name ="format-detection" content ="telephone=no" /> <meta http-equiv ='Pragma' content ='no-cache' /> <meta http-equiv ="Cache-Control" content ="no-cache, must-revalidate" /> <meta name ="description" content ="emui webui 6.0" /> <meta name ="author" content ="emui webui 6.0" />
Then, a request is made to /api/webserver/token
to obtain a token.
Sending
1 2 3 4 <?xml version="1.0" encoding="UTF-8" ?> <response > <token > kixxpBushl0g7cwfKBWo8f5x0rUQZ0343GfPcY89AmPAmGe8Rvuz7zgFgadcINsm</token > </response >
Next, with a part of the token, a request is made to /api/user/challenge_login
.
Sending headers
1 __RequestVerificationToken : 3GfPcY89AmPAmGe8Rvuz7zgFgadcINsm
Sending
1 2 3 4 5 6 <?xml version: "1.0" encoding="UTF-8" ?> <request > <username > admin</username > <firstnonce > 949fc48d7bd5704f508f89303ea95bec4a7902b842b70c3ff8e4a856bf2936b3</firstnonce > <mode > 1</mode > </request >
Response headers
1 __RequestVerificationToken : GvRIT0BRzfKnvh4jPJHbwEHptVKu0pN1
Response
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8" ?> <response > <salt > ce9240656b67c976d81626aecceb3430fac98ddce1b8bb249f9fd742d0ecd01a</salt > <modeselected > 1</modeselected > <servernonce > 949fc48d7bd5704********SOEVzdqxyVkdOhVKODlspu</servernonce > <newType > 0</newType > <iterations > 1000</iterations > </response >
Finally, /api/user/authentication_login
is called to execute the login.
Sending headers
1 __RequestVerificationToken : GvRIT0BRzfKnvh4jPJHbwEHptVKu0pN1
Sending
1 2 3 4 5 <?xml version: "1.0" encoding="UTF-8" ?> <request > <clientproof > a6cba44883fd3c1efad6644d2ad4d8a03aad825e58da8e451be0494803490b5c</clientproof > <finalnonce > 949fc48d7bd5704f508f89303ea95bec4a7902b842b70c3ff8e4a856bf2936b36rDZBBWmHpSOEVzdqxyVkdOhVKODlspu</finalnonce > </request >
Response headers
1 2 3 __RequestVerificationToken : 60vwrcRmApKTXp23fXrMggXOKmwfaZpw#YTXJyp0GFYdE6yCRY2PXRhSePegBin0z#OxPBLp9C30mt5ATFdLhOXl7twjSg903W#Xpdf50IOyMOJ4sKiz0u4Q3WGvonhsgmB#aHs0u9wwWIUQyh8GniyAr63d0l75gvMo#ktV97hCbe1mbfBNUs0YV1r6ql5t0j5YB#5yZM9YazQzMNwFBBKLInwXgMR8QLu8wU#ggtvwsAOaU7QeuRATODQgRtyPfh0UOND#drH0yfkP078zY59OoitQPt8kd3wq6lL6#9u5P2khD8EXlbV64tfvSm5UN0KvHgZsr#7iI4fQKHpkvqVO0VLM0dmpbe8CUhRNSf#l83rMlKJPgdrUtBYzd3J4bahpvVdBpff#cARzgMu9e0IcKRjuNbq1giCFGrgHdsZJ#CueDzDma4ND0MFOWpXudq4rOe9geXCms#VZ105qsscX06ovVkV4uC4SrqxQvQD1eg#JK7Vho4MAh2aWk3Aq7bdxGXsQQIC4L8E#cMQ6I9xF0qEEdrAGPXErdW0kn9Qeg7HK#cJqgObOorgKTFlOmN0yLWSAQkvQKlcQM#4HvCvwyahOk9COjx4Iia0Hiz85h0oacZ#n0iPS1JYqo2nbat5JPfUzz3rJkn2cDzP#gnuOtEoRggG3iQOr1r8ledTG8V49RsI5#X08XP2buIl3AkkmSkZwirGvr5jvLMCRL#VqcfsbxrgoNS8CLU7IzpXbA4xkAu9kGm#00sIkIn1AdP53Jk1i7M5AshO6P7kSDlY#bytRkFGTpuEWW0zT5VIfRb52tYdp0h1o#yyNj0UBaPK9cCLZiDpYSZZD5fTSOZ25Z#9nD7B9U1tJXGUW306mD3R6d39uux69CP#8RqOYuAeV03Dk8Cs3azMuQm6X28HLBNm#08bcN2unNt4GToLJhYNch0IcRnTfxkvK#nfRmgrgDXnYjypb2GjaZrV021Lik98Vj#nYWeb59Ga6vHmGW0Yv0Vwp0cNFMkMTDz#G6rcmJnZhFTVjFxYXH7WLETJ0PY1snwk __RequestVerificationTokenone : 60vwrcRmApKTXp23fXrMggXOKmwfaZpw __RequestVerificationTokentwo : YTXJyp0GFYdE6yCRY2PXRhSePegBin0z
Response
1 2 3 4 5 6 7 <?xml version="1.0" encoding="UTF-8" ?> <response > <serversignature > 0289656ca121a4167f79858042119d8dca68d1615824ab59e63873f8e7041497</serversignature > <rsapubkeysignature > 7d634a04da935fb951889f5dc71abf550028c4e0399116acd826b7115e49c1ca</rsapubkeysignature > <rsae > 010001</rsae > <rsan > e75a0f874d****省略很多字*****58b2f7eb9</rsan > </response >
Finally, it redirects to /html/content.html
.
The entire process is similar to the previous WS5200 router, with the only difference being the use of XML instead of JSON for communication.
Step by step breakdown Handling the login part In the login part, it can be seen that it is mostly unrelated to index.html
. The first __RequestVerificationToken
comes from the token starting from the 32nd character. In each step, the current token is replaced with response.header.__RequestVerificationToken
, and accessing index.html
is only for initializing the cookies.
1 2 3 4 <?xml version="1.0" encoding="UTF-8" ?> <response > <token > kixxpBushl0g7cwfKBWo8f5x0rUQZ034【3GfPcY89AmPAmGe8Rvuz7zgFgadcINsm】 // 第32位开始的内容</token > </response >
The encryption method for login should be consistent with WS5200. The token retrieval part will not be described here. Based on the firstnonce
in the login
part, which is a random 64-character string according to index.js
, the login code can be written as follows. From the console output, it can be seen that the encryption method is consistent with the 5200 router.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private static boolean initToken () { Request request = new Request .Builder().url(URL + "/html/index.html" ).get().build(); try (Response response = okHttpClient.newCall(request).execute(); ResponseBody responseBody = response.body() != null ? response.body() : null ) { if (response.code() == 200 ) { String cookiePath = response.header("Set-Cookie" ); int cookieSplit = cookiePath.indexOf(";" ); cookie = cookiePath.substring(0 , cookieSplit); String token = findFromXml(accessRouter("/api/webserver/token" , "" ), "token" ); csrfToken = token.substring(32 ); System.out.println("Huawei init 4G router version successful!" ); return true ; } } catch (Exception e) { e.printStackTrace(); } return false ; } private static boolean doLogin () throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { String firstNonce = randomNonce(); String msg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><username>admin</username><firstnonce>" + firstNonce + "</firstnonce><mode>1</mode></request>" ; String challengeResponse = accessRouter("/api/user/challenge_login" , msg); if (isNull(challengeResponse)) { return false ; } int iterations = Integer.parseInt(findFromXml(challengeResponse, "iterations" )); String salt = findFromXml(challengeResponse, "salt" ); String serverNonce = findFromXml(challengeResponse, "servernonce" ); String authMessage = firstNonce + "," + serverNonce + "," + serverNonce; KeySpec spec = new PBEKeySpec (PASSWORD.toCharArray(), hexToByteArray(salt), iterations, 256 ); SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256" ); byte [] salted = f.generateSecret(spec).getEncoded(); byte [] clientKey = getHmac("Client Key" , salted); byte [] storedKey = MessageDigest.getInstance("SHA-256" ).digest(clientKey); byte [] authKey = getHmac(authMessage, storedKey); for (int i = 0 ; i < clientKey.length; i++) { clientKey[i] = (byte ) (clientKey[i] ^ authKey[i]); } String clientProof = bytesToHex(clientKey); String loginMsg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><clientproof>" + clientProof + "</clientproof><finalnonce>" + serverNonce + "</finalnonce></request>" ; String loginRespMsg = accessRouter("/api/user/authentication_login" , loginMsg); System.out.println(loginRespMsg); return false ; }
Console output
1 2 3 4 5 6 7 "C:\Program Files\Ja.....jar com.ruterfu.test.Huawei4GRouterAccessMain Huawei init 4G router version successful! <?xml version=" 1.0 " encoding=" UTF-8 "?><response><serversignature>833580708b5efe211fb99ec96fe788a140a22816ec11a6add3b8538137113a9e</serversignature><rsapubkeysignature>7d634a04da935fb951889f5dc71abf550028c4e0399116acd826b7115e49c1ca</rsapubkeysignature><rsae>010001</rsae><rsan>e75a0f874d93cb0733125296421a0a3d24243f7834d34ea5c4e7502c87646717f21d476c89c01f5da2534430ae229fdf8c1d8f9e5883b0433eb8f8dbb52ea1a9e3338ecb9d4c43de2cce405d94707ef886fb2c31209988ba7ac35210c8820d4df74da8597595746b68db19ea5dab33e98ae159e09ad9955756975a5530042369427e8f42b4516caaf5405fcfa12b43712ed3419b2eb49b60453f764c5393c594919ec602f281d3735032d2e305046d98fa82117651f09ef02b1dab4f755df4cb790ef28443e87f76fe0cfd6550f8ab8862d408ad5f7da7913ced7a1b817974408e16119e8c45a041150c823ad70832c7d33b4f719ef42ee43f7e09f58b2f7eb9</rsan></response> false Process finished with exit code 0
Continuing to analyze the SMS API flow Next, let’s analyze the SMS interface part. After logging in, /html/content.html
is accessed. The content of this webpage is as follows:
1 2 3 4 5 6 7 8 9 10 <head > <meta name ="csrf_token" content ="L4fvnQx0GYalJcxUQUxu70mVHpND2ZlL" > <meta name ="csrf_token" content ="cCK2EjEqAzXI8CeglGGjuIPBIUeUvzDu" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" /> <meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" /> <meta http-equiv ='Pragma' content ='no-cache' /> <meta name ="format-detection" content ="telephone=no" /> <meta http-equiv ="Cache-Control" content ="no-cache, must-revalidate" /> <meta name ="description" content ="emui webui 6.0" /> <meta name ="author" content ="emui webui 6.0" />
Then, a request is made to /api/sms/send-sms
to send an SMS.
Request headers
1 __RequestVerificationToken : 6FW0TNffiu2dqYo47NdoSg1T5IbcUPbp
Request
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version: "1.0" encoding="UTF-8" ?> <request > <Index > -1</Index > <Phones > <Phone > 13**********</Phone > </Phones > <Sca > </Sca > <Content > 1</Content > <Length > 1</Length > <Reserved > 1</Reserved > <Date > 2022-04-15 13:39:42</Date > </request >
Response headers
1 __RequestVerificationToken : uDlKYFpmpNbMwBQohxi0RLKhMqeOskXK
Response
1 2 <?xml version="1.0" encoding="UTF-8" ?> <response > OK</response >
The tokens mentioned above are all different and do not follow any specific pattern. However, since there are multiple requests, it’s likely that some of them use tokens.
Let’s go through each one starting from /html/content.html
. We find:
/api/system/onlineupg
uses a token once.
1 2 请求:__RequestVerificationToken : L4fvnQx0GYalJcxUQUxu70mVHpND2ZlL 响应:__RequestVerificationToken : yUMKeCI7MrYZ070rLgxtwY0MbC0tLLkO
/api/host/info
uses a token once.
1 2 请求:__RequestVerificationToken : cCK2EjEqAzXI8CeglGGjuIPBIUeUvzDu 响应:__RequestVerificationToken : kPxK0M2hRLW7cwAX1p5eoSMH1u5dWO6r
/api/sms/sms-list-contact
uses a token.
1 2 请求:__RequestVerificationToken : yUMKeCI7MrYZ070rLgxtwY0MbC0tLLkO 响应:__RequestVerificationToken : 1g0QZRFK30Ks9AkfrNt0aT0xIAE2xTv3
/api/sms/sms-count-contact
uses a token.
1 2 请求:__RequestVerificationToken : kPxK0M2hRLW7cwAX1p5eoSMH1u5dWO6r 响应:__RequestVerificationToken : 6FW0TNffiu2dqYo47NdoSg1T5IbcUPbp
/api/sms/sms-list-phone
uses a token.
1 2 请求:__RequestVerificationToken : 1g0QZRFK30Ks9AkfrNt0aT0xIAE2xTv3 响应:__RequestVerificationToken : 1VbQWlAWROiyTSvEVcXMYPvlxJ010920
The pattern is clear. Starting from the login, the previous token is no longer used for the next request. Instead, the token from two steps ago is used, and the initial token is in content.html
.
1 2 3 4 5 6 7 8 content.html 返回token1, token2 /api/system/onlineupg 使用token1,返回token3 /api/host/info 使用token2,返回token4 /api/sms/sms-list-contact 使用token3,返回token5 /api/sms/sms-count-contact 使用token4,返回token6 /api/sms/sms-list-phone 使用token5,返回token7 /api/sms/send-sms 使用token6,返回token8 以此类推
With the above pattern, let’s make some code modifications Here, I’m using a stack. When a request returns a token, we pop the current token, add the new token, and include the SMS code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public List<String> sendSms (List<String> phoneNumber, String message) { String sendBody = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><Index>-1</Index><Phones><Phone>" + String.join("," , phoneNumber) + "</Phone></Phones><Sca></Sca><Content>" + message + "</Content><Length>" + message.length() + "</Length><Reserved>1</Reserved><Date>" + new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ).format(System.currentTimeMillis()) + "</Date></request>" ; String body = accessRouter("/api/sms/send-sms" , sendBody); if (body != null && body.contains("<response>OK</response>" )) { try { Thread.sleep(4500 ); } catch (InterruptedException e) { e.printStackTrace(); } String resultString = accessRouter("/api/sms/send-status" , "" ); int index = resultString == null ? -1 : resultString.indexOf("<SucPhone>" ); int index2 = index == -1 ? -1 : resultString.indexOf("</SucPhone>" ); if (index >= 0 && index2 > index) { String successBody = resultString.substring(index + 10 , index2).trim(); List<String> successList = Arrays.asList(split(successBody, "," )); return phoneNumber.stream().filter(successList::contains).collect(Collectors.toList()); } } return null ; }
The response for send-status
is as follows:
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="utf-8" ?> <response > <Phone > </Phone > <SucPhone > 13000000000</SucPhone > <FailPhone > </FailPhone > <TotalCount > 1</TotalCount > <CurIndex > 1</CurIndex > </response >
Test Results Let’s write a Main
method.
1 2 3 4 5 public static void main (String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { if (initToken() && doLogin()) { System.out.println(sendSms(Collections.singletonList("13000000000" ), "这是用接口发出去的短信" ).toString()); } }
Console output
1 2 3 4 5 Huawei init 4G router version successful! Huawei 4G Router Login successful [13000000000] Process finished with exit code 0
Received on the phone
Oh, don’t forget to write an exit method. I won’t go into detail about the API endpoint and parameters for the exit method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static boolean logout() { try { String msg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><Logout>1</Logout></request>"; String challenge = accessRouter("/api/user/logout", msg); boolean success = challenge != null && challenge.contains("<response>OK</response>"); if(success) { System.out.println("Huawei 4G router logout successful"); } else { System.out.println("Huawei 4G router logout failed, response " + (challenge == null ? "NULL" : challenge)); } return success; } catch (Exception e) { e.printStackTrace(); } return false; }
Finally, here’s the complete code With the Main
method, we can create a Spring Boot interface. Since we can already send SMS, it would be great to read messages from the router periodically and automate the response. Ha-ha.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 package com.ruterfu.test;import okhttp3.*;import javax.crypto.Mac;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.PBEKeySpec;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.security.InvalidKeyException;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.security.spec.InvalidKeySpecException;import java.security.spec.KeySpec;import java.text.SimpleDateFormat;import java.util.*;import java.util.stream.Collectors;public class Huawei4GRouterAccessMain { private static final Stack<String> csrfToken = new Stack <>(); private static final OkHttpClient okHttpClient = new OkHttpClient (); private static String cookie; private static final String URL = "http://192.168.8.1" ; private static final String PASSWORD = "路由器密码" ; public static void main (String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { if (initToken() && doLogin()) { System.out.println(sendSms(Collections.singletonList("手机号码" ), "短信内容" ).toString()); logout(); } } private static boolean initToken () { Request request = new Request .Builder().url(URL + "/html/index.html" ).get().build(); try (Response response = okHttpClient.newCall(request).execute(); ResponseBody responseBody = response.body() != null ? response.body() : null ) { if (response.code() == 200 ) { String cookiePath = response.header("Set-Cookie" ); int cookieSplit = cookiePath.indexOf(";" ); cookie = cookiePath.substring(0 , cookieSplit); String token = findFromXml(accessRouter("/api/webserver/token" , "" ), "token" ); csrfToken.push(token.length() > 32 ? token.substring(32 ) : "" ); System.out.println("Huawei init 4G router version successful!" ); return true ; } } catch (Exception e) { e.printStackTrace(); } return false ; } private static boolean doLogin () throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { String firstNonce = randomNonce(); String msg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><username>admin</username><firstnonce>" + firstNonce + "</firstnonce><mode>1</mode></request>" ; String challengeResponse = accessRouter("/api/user/challenge_login" , msg); if (isNull(challengeResponse)) { return false ; } int iterations = Integer.parseInt(findFromXml(challengeResponse, "iterations" )); String salt = findFromXml(challengeResponse, "salt" ); String serverNonce = findFromXml(challengeResponse, "servernonce" ); String authMessage = firstNonce + "," + serverNonce + "," + serverNonce; KeySpec spec = new PBEKeySpec (PASSWORD.toCharArray(), hexToByteArray(salt), iterations, 256 ); SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256" ); byte [] salted = f.generateSecret(spec).getEncoded(); byte [] clientKey = getHmac("Client Key" , salted); byte [] storedKey = MessageDigest.getInstance("SHA-256" ).digest(clientKey); byte [] authKey = getHmac(authMessage, storedKey); for (int i = 0 ; i < clientKey.length; i++) { clientKey[i] = (byte ) (clientKey[i] ^ authKey[i]); } String clientProof = bytesToHex(clientKey); String loginMsg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><clientproof>" + clientProof + "</clientproof><finalnonce>" + serverNonce + "</finalnonce></request>" ; String loginRespMsg = accessRouter("/api/user/authentication_login" , loginMsg); if (loginRespMsg != null && loginRespMsg.contains("serversignature" )) { csrfToken.clear(); Request request = new Request .Builder().url(URL + "/html/content.html" ) .addHeader("Cookie" , cookie) .get().build(); try (Response response = okHttpClient.newCall(request).execute(); ResponseBody responseBody = response.body() != null ? response.body() : null ) { if (response.code() == 200 ) { String bodyString = responseBody == null ? null : responseBody.string(); if (bodyString != null && !bodyString.contains("EMUI.RebootController.rebootInfo" )) { System.err.println("Huawei 4G Router Login failed, cannot access content.html" ); return false ; } int start = bodyString == null ? -1 : bodyString.indexOf("<head>" ); int end = start == -1 ? -1 : bodyString.indexOf("</head>" ); if (start >= 0 && end > start) { String headerContent = bodyString.substring(start + 6 , end); String[] singleLine = split(headerContent, "\n" ); for (String s : singleLine) { if (s.contains("<meta" ) && s.contains("csrf_token" )) { int startC = s.indexOf("content=\"" ); int endC = startC == -1 ? -1 : s.indexOf("\">" ); if (startC >= 0 && endC > startC) { csrfToken.push(s.substring(startC + 9 , endC)); } } } System.out.println("Huawei 4G Router Login successful" ); return true ; } } } catch (Exception e) { e.printStackTrace(); } } return false ; } public static List<String> sendSms (List<String> phoneNumber, String message) { String sendBody = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><Index>-1</Index><Phones><Phone>" + String.join("," , phoneNumber) + "</Phone></Phones><Sca></Sca><Content>" + message + "</Content><Length>" + message.length() + "</Length><Reserved>1</Reserved><Date>" + new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ).format(System.currentTimeMillis()) + "</Date></request>" ; String body = accessRouter("/api/sms/send-sms" , sendBody); if (body != null && body.contains("<response>OK</response>" )) { try { Thread.sleep(4500 ); } catch (InterruptedException e) { e.printStackTrace(); } String resultString = accessRouter("/api/sms/send-status" , "" ); int index = resultString == null ? -1 : resultString.indexOf("<SucPhone>" ); int index2 = index == -1 ? -1 : resultString.indexOf("</SucPhone>" ); if (index >= 0 && index2 > index) { String successBody = resultString.substring(index + 10 , index2).trim(); List<String> successList = Arrays.asList(split(successBody, "," )); return phoneNumber.stream().filter(successList::contains).collect(Collectors.toList()); } } return null ; } public static boolean logout () { try { String msg = "<?xml version: \"1.0\" encoding=\"UTF-8\"?><request><Logout>1</Logout></request>" ; String challenge = accessRouter("/api/user/logout" , msg); boolean success = challenge != null && challenge.contains("<response>OK</response>" ); if (success) { System.out.println("Huawei 4G router logout successful" ); } else { System.out.println("Huawei 4G router logout failed, response " + (challenge == null ? "NULL" : challenge)); } return success; } catch (Exception e) { e.printStackTrace(); } return false ; } private static String accessRouter (String url, String body) { Request.Builder request = new Request .Builder().url(URL + url).header("Cookie" , cookie); if (isNull(body)) { request.get(); } else { request.post(RequestBody.create(body, MediaType.parse("application/x-www-form-urlencoded" ))); } if (csrfToken.size() > 0 ) { request.header("__RequestVerificationToken" , csrfToken.pop()); } try (Response response = okHttpClient.newCall(request.build()).execute(); ResponseBody responseBody = response.body() != null ? response.body() : null ) { if (response.code() == 200 ) { String csrfTokenInHeader = response.header("__RequestVerificationToken" ); if (!isNull(csrfTokenInHeader)) { int index = csrfTokenInHeader.indexOf("#" ); csrfToken.push(index == -1 ? csrfTokenInHeader : csrfTokenInHeader.substring(0 , index)); } String newHeader = response.header("Set-Cookie" ); if (newHeader != null ) { cookie = newHeader; } if (responseBody == null ) { return "" ; } else { return responseBody.string(); } } } catch (Exception e) { e.printStackTrace(); } return null ; } private static String findFromXml (String xml, String key) { if (isNull(xml)) { return "" ; } else { String start = "<" + key + ">" ; int startI = xml.indexOf(start); int endI = startI == -1 ? -1 : xml.indexOf("</" + key + ">" ); if (startI >= 0 && endI > startI) { return xml.substring(startI + start.length(), endI).trim(); } return "" ; } } private static boolean isNull (String s) { return s == null || s.length() == 0 ; } private static String randomNonce () { String rand = "abcdef1234567890" ; Random random = new Random (); StringBuilder sb = new StringBuilder (); for (int i = 0 ; i < 64 ; i++) { int number = random.nextInt(rand.length()); sb.append(rand.charAt(number)); } return sb.toString(); } private static byte [] getHmac(String key, byte [] input) throws NoSuchAlgorithmException, InvalidKeyException { String hmacName = "HmacSHA256" ; SecretKeySpec secretKeySpec = new SecretKeySpec (key.getBytes(StandardCharsets.US_ASCII), hmacName); Mac mac = Mac.getInstance(hmacName); mac.init(secretKeySpec); mac.update(input); return mac.doFinal(); } public static byte [] hexToByteArray(String inHex){ int hexLength = inHex.length(); byte [] result; if (hexLength % 2 == 1 ){ hexLength++; result = new byte [(hexLength / 2 )]; inHex = "0" + inHex; }else { result = new byte [(hexLength / 2 )]; } int j=0 ; for (int i = 0 ; i < hexLength; i += 2 ){ result[j]=(byte )Integer.parseInt(inHex.substring(i, i + 2 ),16 ); j++; } return result; } public static String bytesToHex (byte [] bytes) { StringBuffer sb = new StringBuffer (); for (int i = 0 ; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF ); if (hex.length() < 2 ){ sb.append(0 ); } sb.append(hex); } return sb.toString(); } private static String[] split(String str, String token) { StringTokenizer st = new StringTokenizer (str, token); String[] s = new String [st.countTokens()]; int t = 0 ; while (st.hasMoreTokens()) { if (t < s.length) { s[t++] = st.nextToken().trim(); } } return s; } }