package com.xiaomi.xms.sales.xmsf.miui.utils;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class CloudCoder {
private static final String RC4_ALGORITHM_NAME = "RC4";
/**
* Helper class to instantiate an AES cipher with Xiaomi-specified format.
*
* @param aesKey AES key
* @param opMode {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}
* @return the result cipher or null if failed.
* @see Cipher
*/
public static Cipher newAESCipher(String aesKey, int opMode) {
byte[] keyRaw = Base64.decode(aesKey, Base64.NO_WRAP);
Cipher cipher;
SecretKeySpec keySpec = new SecretKeySpec(keyRaw, "AES");
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(
"0102030405060708".getBytes());
cipher.init(opMode, keySpec, iv);
return cipher;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
public static Cipher newRC4Cipher(byte[] rc4Key, int opMode) {
Cipher cipher;
SecretKeySpec keySpec = new SecretKeySpec(rc4Key, RC4_ALGORITHM_NAME);
try {
cipher = Cipher.getInstance(RC4_ALGORITHM_NAME);
cipher.init(opMode, keySpec);
return cipher;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
/**
* Compute SHA1 hash value for the string
*
* @param plain plain text. It will be encoded to BASE64 before hash
* @return BASE64 encoded hash value
*/
public static String hash4SHA1(String plain) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
return Base64.encodeToString(md.digest(plain.getBytes("UTF-8")),
Base64.NO_WRAP);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// this should never be reached
throw new IllegalStateException("failed to SHA1");
}
/**
* Generate signature for the request.
*
* @param method http request method. GET or POST
* @param requestUrl the full request url. e.g.: http://api.xiaomi.com/getUser?id=123321
* @param params request params. This should be a TreeMap because the
* parameters are required to be in lexicographic order.
* @param security AES secret key. Must NOT be null.
* @return hash value for the values provided
*/
public static String generateSignature(String method, String requestUrl,
Map<String, String> params, String security) {
if (TextUtils.isEmpty(security)) {
throw new InvalidParameterException("security is not nullable");
}
List<String> exps = new ArrayList<String>();
if (method != null) {
exps.add(method.toUpperCase());
}
if (requestUrl != null) {
Uri uri = Uri.parse(requestUrl);
exps.add(uri.getEncodedPath());
}
if (params != null && !params.isEmpty()) {
final TreeMap<String, String> sortedParams
= new TreeMap<String, String>(params);
Set<Map.Entry<String, String>> entries = sortedParams.entrySet();
for (Map.Entry<String, String> entry : entries) {
exps.add(String.format("%s=%s", entry.getKey(),
entry.getValue()));
}
}
exps.add(security);
boolean first = true;
StringBuilder sb = new StringBuilder();
for (String s : exps) {
if (!first) {
sb.append('&');
}
sb.append(s);
first = false;
}
return hash4SHA1(sb.toString());
}
}