二維碼在企業移動中的應用
轉載本文需註明出處:EAWorld,違者必究。
引言:
二維碼是自動識別中的一項重要技術,也是物聯網產業的關鍵、核心技術之一。二維碼作為高效的資訊載體,結合移動互聯技術將人-物相連、物-物相連,並在我們的日常生活中得到了廣泛的應用。
近些年來,企業也在以“資料+連線”的思路,嘗試著二維碼技術在企業場景中的應用和落地。本文將從應用場景、生成與解析、完整性校驗和安全性保證等幾個方向闡述二維碼在企業中的技術實現和應用。
目錄:
一、二維碼的發展與應用場景
二、二維碼管理系統的提出與設計
三、如何實現二維碼管理系統
一、二維碼的發展與應用場景
- 二維碼的發展
說到二維碼我想大家都不會陌生,起初,二維碼是日本電裝公司為了解決傳統條形碼資訊不能容納高精度的汽車零配件資訊的問題而研究的替代品,這一次升級讓原本只能儲存20位左右的傳統一維條形碼一躍成為最多能夠儲存7089字(僅用數字時)左右的二維碼,足足翻了300倍左右。
日本人在當初發明的時候怎麼也沒有想到,正是這一次不起眼的發明,卻在如今成為了中國移動網際網路飛速崛起浪潮中的弄潮兒。據《中國網際網路絡發展狀況統計報告》統計,2016年中國人平均每天使用微信掃碼就達10億人次,使用支付寶掃碼達到5億人次。而截止至2018年6月,掃碼使用共享單車的使用者數量達到2.45億,佔總體網民的30.6%。
- 企業中的應用場景
隨著網路、二維碼和二維碼讀寫裝置等技術逐漸成熟,企業也在探索二維碼在不同場景下的應用。例如:二維碼門禁,資產標籤,上班打卡,會議簽到,員工身份標識,食堂就餐,智慧停車場等。
以企業訪客的場景為例,現在各企業間、企業內部各部門之前的合作往來越發密切,為減少訪客在拜訪排隊登記的時間,二維碼訪客管理應運而生。通過二維碼訪客管理服務,訪客來訪之前可以先提交拜訪資訊給受訪人,在受訪人核實後由二維碼訪客管理服務為訪客生成對應的訪客二維碼。訪客在來訪時可以通過門禁系統的二維碼掃描進入具有指定許可權的辦公區域訪問企業使用者人員。
再或者以企業組織大型會議為例,組織方往往面臨會議通知邀請、會議簽到、會議材料發放等等繁雜的流程環節且耗費大量的時間和人工。但在採用二維碼會議服務後,會前系統會為每個與會人員傳送定製的二維碼邀請函,與會人員可以通過掃描二維碼邀請函瞭解會議相關安排資訊。到達會議現場後可以通過二維碼邀請函進行身份識別,掃碼簽到,並隨時瞭解會議議程,提升了參會人員的便捷體驗感受。而對於會議的組織方來說可以很方便統計會議參會情況並在會後做相關的調查工作。二維碼會議服務相對傳統會議組織模式來說簡化了會議組織流程,減輕了組織方的工作壓力的同時提升了效率。
二維碼在企業中還有很多類似於上面兩個故事的應用範例,不難看出,在企業的管理和發展中,二維碼的應用不僅提高了生產效率和管理效率,同時也提升了員工的工作體驗。
二、二維碼管理系統的提出與設計
- 二維碼管理系統的提出
在企業的發展中,任何技術和系統都是解決企業實際問題而產生的。對企業來講,儘管二維碼有如此寬泛的應用場景,但二維碼應該如何去統一管理呢?如果是由各個業務系統去單獨完成二維碼的處理工作,既會產生大量重複開發工作,又無法有效的統一管理。為此,提出二維碼管理系統。按照預定好的內容規範和生成呼叫規範,為企業各個業務系統提供標準的二維碼管理服務,使各業務系統可以更加專注於二維碼的實際應用落地。
- 二維碼管理系統的設計
在二維碼系統的設計層面,我們既要考慮到使用者呼叫系統的難度,又要保證系統的安全性和規範性。
- 即插即用
- 二維碼內容加密
- 內容完裝性保證
- 生命期機制
- 二維碼能力SDK易用性
- 統一二維碼標準規範
二維碼管理系統是將二維碼的生成、驗證、加解密、解析等通用能力進行抽象,採用面向物件的設計方法,達到系統的內部高內聚,對外低耦合的目的。以元件化的方式進行封裝,充分考慮到使用方的二維碼相關能力呼叫場景,為企業的各個業務系統提供簡單易用的二維碼能力。
- 需要考慮的問題
- 二維碼內容洩露或二維碼偽造引起的安全性問題
- 傳輸過程中資料篡改引起的完整性問題;
- 有效期引起的生命期問題。
同時在二維碼管理系統的設計,我們需要特別考慮安全性、完成性和生命期的問題,這也是企業二維碼系統最基礎的要求。為了解決這些問題二維碼管理系統會在二維碼生成時對二維碼內容進行如下處理,首先根據呼叫業務系統傳遞的有效期欄位生成對應的有效期校驗欄位按預定規則拼裝在二維碼內容明文後,然後採用雜湊演算法(如SM3演算法、MD5演算法、SHA1演算法)對新增過有效期驗證欄位的二維碼明文進行計算來生成對應的完整性校驗欄位,最後將明文與校驗欄位組合後按二維碼管理系統預設金鑰進行對稱加密(如DES算噠、AES演算法、SM4演算法),最終得到用來生成二維碼的內容並生成二維碼圖片返回給業務系統。
二維碼生成
在二維碼內容解析的時候由業務系統通過掃描裝置識別出二維碼內容密文,然後將密文傳遞迴二維碼管理系統。二維碼管理系統在接收到掃描出的二維碼密文時先通過之前加密時使用的對稱演算法的逆演算法得到帶有校驗欄位的明文,然後通過之前用來生成校驗欄位的非對稱演算法(如SM3演算法、MD5演算法、SHA1演算法)檢驗明文與檢驗欄位是否符合,保證了二維碼完整性的校驗,最後在通過在明文中預先拼接的有效期欄位進行有效期判斷,若過期失效則返回失效資訊,反之去除有效期欄位返回原始二維碼內容。
二維碼內容解析
通過上述對二維碼內容處理的流程有效地避免了二維碼在使用中的大部分問題。
- 系統場景設計
在設計中我們需要考慮兩種場景,主動掃描和被動掃描。
主動掃描
對於主動掃描的場景,使用者通過二維碼掃描裝置掃描二維碼獲得二維碼密文資訊,傳送至企業的業務系統,由業務系統呼叫二維碼管理系統對密文進行解析、處理,並在獲取到解析結果後進行對應的業務操作並反饋給使用者最終處理結果。
被動掃描
對於被動掃描的場景,在使用者需要被掃碼的時候由使用者的二維碼展示裝置傳送請求至業務系統,業務系統收到請求後呼叫二維碼管理系統對應的二維碼生成服務,生成二維碼,並將二維碼返回給顯示裝置,由顯示裝置顯示給掃描方完成被動掃描動作。最後再由掃描方業務系統執行主動掃描場景中的業務處理流程。
主要參考文件:
《QRCODE》
《系統安全規範》
《二維碼網格矩陣碼(SJ/T 11349-2006)》
《二維碼緊密矩陣碼(SJ/T 11350-2006)》
《GB/T 18284-2000》
三、如何實現二維碼管理系統
- 二維碼的生成
推薦在二維碼標準選擇的時候使用應用最為廣泛的QRCode碼,後續程式碼示例中也會以此為基準。
目前開源的二維碼生成工具包有很多,比如QRCode、jqueryqrcode、zxing、Barcode4j等。這裡選用一個比較常用的由google開發維護的zxing,具體的程式碼可以參考Google程式碼測試包中的MatrixToImageWriter類來輔助開發,可以將該類直接拷貝到原始碼中使用。
生成二維碼關鍵程式碼範例,以Google的zxing為例:
public static BufferedImage toBufferedImage(BitMatrix matrix) {
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, (matrix.get(x, y) ? BLACK : WHITE));
}
}
return image;
}
public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
BufferedImage image = toBufferedImage(matrix);
LogoConfig logoConfig = new LogoConfig();
image = logoConfig.LogoMatrix(image);
if (!ImageIO.write(image, format, file)) {
throw new IOException("Could not write an image of format " + format + " to " + file);
}
}
對於部分業務系統在生成二維碼的時候可能存在需要在二維碼圖片中心新增logo圖片的業務需求,這裡貼出在二維碼中新增logo的示例:
新增logo程式碼示例:
public BufferedImage LogoMatrix(BufferedImage matrixImage, String logoFilePath) throws IOException{
Graphics2D g2 = matrixImage.createGraphics();
int matrixWidth = matrixImage.getWidth();
int matrixHeigh = matrixImage.getHeight();
BufferedImage logo = ImageIO.read(new File(logoFilePath));
g2.drawImage(logo,matrixWidth/5*2,matrixHeigh/5*2, matrixWidth/5, matrixHeigh/5, null);
BasicStroke stroke = new BasicStroke(5,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
g2.setStroke(stroke);
RoundRectangle2D.Float round = new RoundRectangle2D.Float(matrixWidth/5*2, matrixHeigh/5*2,
matrixWidth/5, matrixHeigh/5,20,20);
g2.setColor(Color.white);
g2.draw(round);
BasicStroke stroke2 = new BasicStroke(1,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
g2.setStroke(stroke2);
RoundRectangle2D.Float round2 = new RoundRectangle2D.Float(matrixWidth/5*2+2,
matrixHeigh/5*2+2, matrixWidth/5-4, matrixHeigh/5-4,20,20);
g2.setColor(new Color(128,128,128));
g2.draw(round2);
g2.dispose();
matrixImage.flush() ;
return matrixImage ;
}
- 二維碼內容的加解密
二維碼內容加密主要分為兩部分,其一是生成用於完整性校驗欄位的雜湊演算法,其二是為了明文加密的對稱加密。
常用於完整性檢驗的雜湊演算法有很多種,如Ron Rivest(RSA公司)的訊息摘要演算法MD系列,這其中MD5因為兼具快速和安全兩大特點被廣泛採。同樣的雜湊演算法還有美國國家標準技術研究院(NIST)制定的安全雜湊演算法SHA(Secure Hash Algorithm)系列演算法,國家密碼管理局釋出的雜湊演算法標準SM3等。這裡使用MD5作為程式碼範例:
public static String getMD5Text(String data) {
MessageDigest messageDigest = null;
byte[] srcBytes = data.getBytes();
try {
messageDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
messageDigest.update(srcBytes, 0, srcBytes.length);
BigInteger bigInt = new BigInteger(1, messageDigest.digest());
return bigInt.toString(16);
}
在二維碼密文解析的時候可使用相同方法取原文的MD5值進行檢驗。
二維碼明文加密採用對稱加密演算法,對稱金鑰由二維碼管理系統保管,常見的對稱演算法有DES演算法、AES演算法、IDEA演算法、SM4演算法等,這裡以AES演算法為例。
加密:
public static String encrypt(String content, String password) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 建立密碼器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));// 初始化為加密模式的密碼器
byte[] result = cipher.doFinal(byteContent);// 加密
return Base64.encodeBase64String(result);//通過Base64轉碼返回
} catch (Exception ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
解密:
public static String decrypt(String content, String password) {
try {
//例項化
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
//使用金鑰初始化,設定為解密模式
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
//執行操作
byte[] result = cipher.doFinal(Base64.decodeBase64(content));
return new String(result, "utf-8");
} catch (Exception ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
生成金鑰:
private static SecretKeySpec getSecretKey(final String password) {
//返回生成指定演算法金鑰生成器的 KeyGenerator 物件
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//AES 要求金鑰長度為 128
kg.init(128, new SecureRandom(password.getBytes()));
//生成一個金鑰
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);
// 轉換為AES專用金鑰
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(AESUtil.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
這裡簡要說明了加解密的相關示例,網上也有相關的開源示例,對加解密有興趣的話可以自行深入研究。
關於作者:馮浩,現任普元移動團隊開發工程師,畢業於山東大學(威海),主攻移動原生開發、react native開發、JAVA Web開發。先後參與中國郵政集團移動平臺、國家開發銀行移動應用平臺等專案的開發工作。
關於EAWorld:微服務,DevOps,資料治理,移動架構原創技術分享。長