業務場景
需要生成類似微信openid的字符串,openid 在相同公衆號下,openid前面部分字符是相等的,例如:
某一個公衆號下的三個關注用戶的openid是這樣的:
oB4nYjnoHhuWrPVi2pYLuPjnCaU0
oB4nYjhJHQVaD0PL7qs0W1kL-_ls
oB4nYjvY13SVtaWC-AFztM2f3TlU
注意到,openid
的前面一部分都是 oB4nYj,但具體的算法,微信未公佈。
前面部分,是表示特定的公衆號,後面部分則是該用戶的標識符,該openid在該公衆號下是唯一的。
對於自身業務需求,現在需要一串前半部分標識供應商、後半部分則是隨機碼的字符串券碼,且供應商標識需要加密、長度不能過長。
思考過程
對於前半部分供應商標識
-
需要加密
-
加密後的長度不能過長
對於加密算法,主要有如下幾類算法:
但這些算法加密後的字符串長度都過長。此時就思索,有沒有一種既能滿足安全性高,且加密後的字符串長度也短的算法呢?
對於這種需求,也試驗了很多種算法。RC4算法
就能滿足。【密碼學】RC4加解密原理及其Java和C實現算法
對於後半部分字符串券碼
需要唯一。即生成唯一ID,對此查閱相關資料
主要有幾種解決方案:
- 數據庫自增長序列或字段(美團點評分佈式ID生成系統)
- UUID
- Redis生成ID
- Twitter的snowflake算法(Twitter的雪花算法(snowflake)自增ID)
- 利用zookeeper生成唯一ID
- MongoDB的ObjectId
這些技術方案滿足ID唯一,其中snowflake算法在這些方案中最佳,但生成的ID都過長。
技術方案
- RC4算法加密的三位運營商id(加密後變爲6位)
- uuid(壓縮後變成8位的短字符串)
- 系統本身已有供應商標識,例如:001
- 對該標識用RC4,這樣的好處是加密後的字符串長度短,加密性比較好。提供工具類如下:
package com.ceb.mental.util.IdGen;
import java.io.UnsupportedEncodingException;
/**
* 負責生成ACCESS_TOKEN||REFRESH_TOKEN令牌
*
*/
public class RC4 {
/** ACCESS_TOKEN的加密鑰匙 **/
public static final String ACCESSKEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxACCESSKEY";
/** REFRESH_TOKEN的加密鑰匙 **/
public static final String REFRESHKEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxREFRESHKEY";
/** 加密 **/
public static String encrypt(String data, String key) {
if (data == null || key == null) {
return null;
}
return toHexString(asString(encrypt_byte(data, key)));
}
/** 解密 **/
public static String decrypt(String data, String key) {
if (data == null || key == null) {
return null;
}
return new String(RC4Base(HexString2Bytes(data), key));
}
/** 加密字節碼 **/
public static byte[] encrypt_byte(String data, String key) {
if (data == null || key == null) {
return null;
}
byte b_data[] = data.getBytes();
return RC4Base(b_data, key);
}
private static String asString(byte[] buf) {
StringBuffer strbuf = new StringBuffer(buf.length);
for (int i = 0; i < buf.length; i++) {
strbuf.append((char) buf[i]);
}
return strbuf.toString();
}
private static byte[] initKey(String aKey) {
byte[] b_key = aKey.getBytes();
byte state[] = new byte[256];
for (int i = 0; i < 256; i++) {
state[i] = (byte) i;
}
int index1 = 0;
int index2 = 0;
if (b_key == null || b_key.length == 0) {
return null;
}
for (int i = 0; i < 256; i++) {
index2 = ((b_key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
byte tmp = state[i];
state[i] = state[index2];
state[index2] = tmp;
index1 = (index1 + 1) % b_key.length;
}
return state;
}
private static String toHexString(String s) {
String str = "";
for (int i = 0; i < s.length(); i++) {
int ch = (int) s.charAt(i);
String s4 = Integer.toHexString(ch & 0xFF);
if (s4.length() == 1) {
s4 = '0' + s4;
}
str = str + s4;
}
return str;// 0x表示十六進制
}
private static byte[] HexString2Bytes(String src) {
int size = src.length();
byte[] ret = new byte[size / 2];
byte[] tmp = src.getBytes();
for (int i = 0; i < size / 2; i++) {
ret[i] = uniteBytes(tmp[i * 2], tmp[i * 2 + 1]);
}
return ret;
}
private static byte uniteBytes(byte src0, byte src1) {
char _b0 = (char) Byte.decode("0x" + new String(new byte[] { src0 })).byteValue();
_b0 = (char) (_b0 << 4);
char _b1 = (char) Byte.decode("0x" + new String(new byte[] { src1 })).byteValue();
byte ret = (byte) (_b0 ^ _b1);
return ret;
}
private static byte[] RC4Base(byte[] input, String mKkey) {
int x = 0;
int y = 0;
byte key[] = initKey(mKkey);
int xorIndex;
byte[] result = new byte[input.length];
for (int i = 0; i < input.length; i++) {
x = (x + 1) & 0xff;
y = ((key[x] & 0xff) + y) & 0xff;
byte tmp = key[x];
key[x] = key[y];
key[y] = tmp;
xorIndex = ((key[x] & 0xff) + (key[y] & 0xff)) & 0xff;
result[i] = (byte) (input[i] ^ key[xorIndex]);
}
return result;
}
/**
* 字符串轉換成十六進制字符串
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
}
return sb.toString();
}
/**
*
* 十六進制轉換字符串
*
* @throws UnsupportedEncodingException
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
public static void main(String[] args) {
String data="002";
System.out.println("加密結果 "+RC4.encrypt(data, ACCESSKEY));
System.out.println("解密結果 "+RC4.decrypt(RC4.encrypt(data, ACCESSKEY), ACCESSKEY));
}
}
-
生成UUID,網上找的都是生成32位的字符串,長度過長,這邊對生成的UUID進行壓縮,最終生成8位的UUID,代碼如下:
package com.bridgeintelligent.ercp.common.utils.IdGen; import java.util.List; import java.util.UUID; import java.util.Vector; public class IdGenUtil { /** * @param num * @return * @function 生成num位的隨機字符串(數字 、 大寫字母隨機混排) */ public static String createBigSmallLetterStrOrNumberRadom(int num) { String str = ""; for (int i = 0; i < num; i++) { int intVal = (int) (Math.random() * 58 + 65); if (intVal >= 91 && intVal <= 96) { i--; } if (intVal < 91 || intVal > 96) { if (intVal % 2 == 0) { str += (char) intVal; } else { str += (int) (Math.random() * 10); } } } return str; } public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; /** * 獲取8位的uuid * * @return */ public static String getShortUuid() { StringBuffer stringBuffer = new StringBuffer(); String uuid = UUID.randomUUID().toString().replace("-", ""); for (int i = 0; i < 8; i++) { String str = uuid.substring(i * 4, i * 4 + 4); int strInteger = Integer.parseInt(str, 16); stringBuffer.append(chars[strInteger % 0x3E]); } return stringBuffer.toString(); } public static String idGen(String provideId) { // 先對提供商進行加密 String encryptPID = RC4.encrypt(provideId, RC4.ACCESSKEY); //隨機字符串 // String radom = createBigSmallLetterStrOrNumberRadom(8); String radom = getShortUuid(); return encryptPID + radom; } public static List<String> idBatchGen(String provideId, int idNum) { List<String> idList = new Vector<>(); for (int i = 0; i < idNum; i++) { idList.add(idGen(provideId)); } return idList; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { System.out.println(idGen("001")); } System.out.println(RC4.decrypt("0247BC")); System.out.println("======================="); System.out.println(idBatchGen("001", 10000)); } }
-
其中主要包括生成8位的uuid方法,傳入運營商ID,即可生成相應的券碼,運行結果如下:
其中前6位是加密後的字符串,與預期結果一致。後八位是UUID壓縮後的字符串。