本文做者:阿里移動安全@伊樵,@舟海html
Android開發中,不免會遇到須要加解密一些數據內容存到本地文件、或者經過網絡傳輸到其餘服務器和設備的問題,但並非使用了加密就絕對安全了,若是加密函數使用不正確,加密數據很容易受到逆向破解攻擊。還有不少開發者沒有意識到的加密算法的問題。java
密碼學的三大做用:加密( Encryption)、認證(Authentication),鑑定(Identification)android
加密:防止壞人獲取你的數據。算法
認證:防止壞人修改了你的數據而你卻並無發現。api
鑑權:防止壞人假冒你的身份。安全
明文、密文、密鑰、對稱加密算法、非對稱加密算法,這些基本概念和加密算法原理就不展開敘述了。服務器
Android SDK使用的API和JAVA提供的基本類似,由 Java Cryptography Architecture (JCA,java加密體系結構) ,Java Cryptography Extension (JCE,Java加密擴展包) ,Java Secure Sockets Extension(JSSE,Java安全套接字擴展包),Java Authentication and Authentication Service(JAAS,Java 鑑別與安全服務)組成。網絡
JCA提供基本的加密框架,如證書、數字簽名、消息摘要和密鑰對產生器,對應的Android API中的如下幾個包:框架
java.security java.security.acl java.security.cert java.security.interfaces java.security.spec
JCE擴展了JCA,提供了各類加密算法、摘要算法、密鑰管理等功能,對應的Android API中的如下幾個包:less
javax.crypto javax.crypto.interfaces javax.crypto.spec
JSSE提供了SSL(基於安全套接層)的加密功能,使用HTTPS加密傳輸使用,對應的Android API主要是java.net.ssl包中。
JAAS 提供了在Java平臺上進行用戶身份鑑別的功能。對應的Android API主要在如下幾個包:
javax.security.auth javax.security.auth.login javax.security.auth.callback javax.security.auth.x500
它們其實只是一組接口,實際的算法是可由不一樣的Provider提供,Android API默認的Provider主要是是Bouncy Castle和OpenSSL。
此外Android API還提供了android.security和android.security.keystore(API 23新增)來管理keychain和keystore。
Base64編碼算法是一種用64個字符(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
)來表示任意二進制數據的方法。在計算機網絡發展的早期,因爲「歷史緣由」,電子郵件不支持非ASCII碼字符,若是要傳送的電子郵件帶有非ASCII碼字符(諸如中文)或者圖片,用戶收到的電子郵件將會是一堆亂碼,所以發明了Base64編碼算法。至於爲什麼會亂碼?請你們自行Google。在加解密算法中,原始的數據和加密後的數據通常也是二進制數據,爲了避免傳輸出錯,方便保存或者調試代碼,通常須要對加密後的數據進行base64編碼。
Android提供了Base64編碼的工具類android.util.Base64,能夠直接使用,不用本身去實現base64編碼的算法了。 如:
byte[] output = sha.digest(); String result = Base64.encodeToString(output, Base64.DEFAULT);
開發者建議:base64只是一種編碼方式,並非一種加密算法,不要使用base64來加密數據。
在Android加密算法中須要隨機數時要使用SecureRandom來獲取隨機數。 如:
SecureRandom sr = new SecureRandom(); byte[] output = new byte[16]; sr.nextBytes(output);
注意不要給SecureRandom設置種子。調用seeded constructor或者setSeed(byte[])是不安全的。SecureRandom()默認使用的是dev/urandom做爲種子產生器,這個種子是不可預測的。
開發者建議:
不要使用Random類來獲取隨機數。
在使用SecureRandom時候,不要設置種子。使用如下函數設置種子都是有風險的:
SecureRandom.SecureRandom(byte[] seed) SecureRandom.setSeed(long seed) SecureRandom.setSeed(byte[] seed)
Hash算法是指任意長度的字符串輸入,此算法能給出固定n比特的字符串輸出,輸出的字符串通常稱爲Hash值。
具備如下兩個特色:
抗碰撞性:尋找兩個不一樣輸入獲得相同的輸出值在計算上是不可行的,須要大約 的時間去尋找到具備相同輸出的兩個輸入字符串。
不可逆:不可從結果推導出它的初始狀態。
抗碰撞性使Hash算法對原始輸入的任意一點更改,都會致使產生不一樣的Hash值,所以Hash算法能夠用來檢驗數據的完整性。咱們常常見到在一些網站下載某個文件時,網站還提供了此文件的hash值,以供咱們下載文件後檢驗文件是否被篡改。
不可逆的特性使Hash算法成爲一種單向密碼體制,只能加密不能解密,能夠用來加密用戶的登陸密碼等憑證。
開發者建議:
建議使用SHA-25六、SHA-3算法。 如使用SHA-256算法對message字符串作哈希:
... byte[] input = message.getBytes(); MessageDigest sha = MessageDigest.getInstance("SHA-256"); sha.update(input); byte[] output = sha.digest(); String result = Base64.encodeToString(output, Base64.DEFAULT);
不建議使用MD二、MD四、MD五、SHA-一、RIPEMD算法來加密用戶密碼等敏感信息。這一類算法已經有不少破解辦法,例如md5算法,網上有不少查詢的字典庫,給出md5值,能夠查到加密前的數據。
MessageDigest md = MessageDigest.getInstance("MD5"); byte[] md5Bytes = md.digest(str.getBytes()); String result = Base64.encodeToString(md5Bytes, Base64.DEFAULT);
不要使用哈希函數作爲對稱加密算法的簽名。
注意:當多個字符串串接後再作hash,要很是小心。**
如:字符串S,字符串T,串接作hash,記爲 H (S||T)。可是有可能發生如下狀況。
如「builtin||securely」 和 「built||insecurely」的hash值是徹底同樣的。
如何修改從而避免上述問題產生? 改成H(length(S) || S || T)或者 H(H(S)||H(T))或者H(H(S)||T)。
實際開發過程當中常常會對url的各個參數,作詞典排序,而後取參數名和值串接後加上某個SECRET字符串,計算出hash值,做爲此URL的簽名,如foo=1, bar=2, baz=3 排序後爲bar=2, baz=3, foo=1,作hash的字符串爲:SECRETbar2baz3foo1,在參數和值之間沒有分隔符,則」foo=bar」和」foob=ar」的hash值是同樣的,」foo=bar&fooble=baz」和」foo=barfooblebaz」同樣,這樣經過精心構造的惡意參數就有可能與正常參數的hash值同樣,從而騙過服務器的簽名校驗。
要確保加密的消息不是別人僞造的,須要提供一個消息認證碼(MAC,Message authentication code)。
消息認證碼是帶密鑰的hash函數,基於密鑰和hash函數。
密鑰雙方事先約定,不能讓第三方知道。
消息發送者使用MAC算法計算出消息的MAC值,追加到消息後面一塊兒發送給接收者。
接收者收到消息後,用相同的MAC算法計算接收到消息MAC值,並與接收到的MAC值對比是否同樣。
開發者建議: 建議使用HMAC-SHA256算法,避免使用CBC-MAC。
HMAC-SHA256例子以下:
//初始化KeyGenerator KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256"); //產生密鑰 SecretKey secretKey = keyGenerator.generateKey(); //獲取密鑰 byte[] key = secretKey.getEncoded(); Log.d(Base64.encodeToString(key, Base64.DEFAULT)); //還原密鑰 SecretKey restoreSecretKey = new SecretKeySpec(key, "HmacSHA256"); //實例化MAC Mac mac = Mac.getInstance(restoreSecretKey.getAlgorithm()); //初始化MAC mac.init(restoreSecretKey); //執行摘要 byte[] hmacSHA256Bytes = mac.doFinal(message.getBytes()); result = Base64.encodeToString(hmacSHA256Bytes, Base64.DEFAULT);
在對稱加密算法中,數據發信方將明文(原始數據)和加密密鑰一塊兒通過特殊加密算法處理後,使其變成複雜的加密密文發送出去。收信方收到密文後,若想解讀原文,則須要使用加密用過的密鑰及相同算法的逆算法對密文進行解密,才能使其恢復成可讀明文。在對稱加密算法中,使用的密鑰只有一個,發收信雙方都使用這個密鑰對數據進行加密和解密,這就要求解密方事先必須知道加密密鑰。
該算法的缺點是,若是一旦密鑰泄漏,那麼加密的內容將都不可信了。
開發者建議:
建議使用AES算法。
DES默認的是56位的加密密鑰,已經不安全,不建議使用。
注意加密模式不要使用ECB模式。ECB模式不安全,說明問題的經典的三張圖片,如 明文是:
用ECB加密模式後:
用CBC加密模式後:
想更深刻的瞭解關於對CBC加密模式的攻擊,可參看:《SSL/TLS協議安全系列:CBC 模式的弱安全性介紹(一)》http://drops.wooyun.org/tips/6619
Android 提供的AES加密算法API默認使用的是ECB模式,因此要顯式指定加密算法爲:CBC或CFB模式,可帶上PKCS5Padding填充。AES密鑰長度最少是128位,推薦使用256位。
//生成KEY KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256); //產生密鑰 SecretKey secretKey = keyGenerator.generateKey(); //獲取密鑰 byte[] keyBytes = secretKey.getEncoded(); Log.d("AES KEY", Base64.encodeToString(keyBytes, 0)); //還原密鑰 SecretKey key = new SecretKeySpec(keyBytes, "AES"); //加密 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] encodeResult = cipher.doFinal(plainText.getBytes()); Log.d("AES encode", Base64.encodeToString(encodeResult, Base64.DEFAULT));
非對稱加密算法須要兩個密鑰:公開密鑰(publickey)和私有密鑰(privatekey)。公開密鑰與私有密鑰是一對,若是用公開密鑰對數據進行加密,只有用對應的私有密鑰才能解密;若是用私有密鑰對數據進行加密,那麼只有用對應的公開密鑰才能解密(這個過程能夠作數字簽名)。
非對稱加密主要使用的是RSA算法。
開發者建議:
注意密鑰長度不要低於512位,建議使用2048位的密鑰長度。使用RSA進行數字簽名的算法,如:
//生成密鑰 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic(); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)keyPair.getPrivate(); //簽名 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded()); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(src.getBytes()); byte[] result = signature.sign();
使用RSA算法作加密,RSA加密算法應使用Cipher.getInstance(RSA/ECB/OAEPWithSHA256AndMGF1Padding),不然會存在重放攻擊的風險。如:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); //公鑰加密 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded()); KeyFactory keyFactory= KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA256AndMGF1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] result = cipher.doFinal(src.getBytes()); ... //私鑰解密 PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded()); KeyFactory keyFactory2 = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory2.generatePrivate(pkcs8EncodedKeySpec); Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA256AndMGF1Padding"); cipher5.init(Cipher.DECRYPT_MODE, privateKey5); byte[] result2 = cipher.doFinal(result);
PBE是一種基於口令的加密算法,其特色是使用口令代替了密鑰,而口令由用戶本身掌管,採用隨機數雜湊多重加密等方法保證數據的安全性。
開發者建議:使用基於口令的加密算法PBE時,生成密鑰時要加鹽,鹽的取值最好來自SecureRandom,並指定迭代次數。如:
//初始化鹽 mSalt = new byte[SALT_LENGTH_BYTES]; SecureRandom sr = new SecureRandom(); sr.nextBytes(mSalt); SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_GENERATOR_MODE); keySpec = new PBEKeySpec(password, salt, KEY_GEN_ITERATION_COUNT, KEY_LENGTH_BITS); secretKey = secretKeyFactory.generateSecret(keySpec);
(以上全部示例算法僅供參考)
幾條原則:
不要本身設計加密算法和協議,使用業界標準的算法。
對稱加密算法不要使用ECB模式,不建議使用DES算法。
要選擇合適長度的密鑰。
要確保隨機數生成器的種子具備足夠的信息熵。
不要使用沒有消息認證的加密算法加密消息,沒法防重放。
當多個字符串拼接後作hash,要很是小心。
當給算法加yan鹽取值時不要過短,不要重複。
使用初始化向量時IV時,IV爲常量的CBC,CFB,GCM等和ECB同樣能夠重放,即採用上一個消息的最後一塊密文做爲下一個消息的IV,是不安全的。
密鑰應遵循的原則
密鑰不能爲常量,應隨機,按期更換,若是加密數據時使用的密鑰爲常量,則相同明文加密會獲得相同的密文,很難防止字典攻擊。
開發同窗要防範密鑰硬編碼的毛病。
而在實際開發中,密鑰如何保存始終是繞不過的坎?若是硬編碼在代碼中容易被逆向,若是放在設備的某個文件,也會被有經驗的破解者逆向找到,在這裏推薦阿里聚安全的安全組件服務,其中的安全加密功能提供了開發者密鑰的安全管理與加密算法實現,保證密鑰的安全性,實現安全的加解密操做。
《Java加密與解密的藝術》
《Android Application Secure Design/Secure Coding Guidebook》
http://netifera.com/research/flickr_api_signature_forgery.pdf
http://nelenkov.blogspot.com/2012/04/using-password-based-encryption-on.html*
瞭解更多技術乾貨和最新安整年報,請點擊這裏