版權是我的,轉載沒有通過我的同意的爬蟲都是傻逼.
假設有user表.里面有id,acount(賬戶,nvarchar(50)),pwd(密碼,nvarchar(50)).
最簡單的實現
渣渣說做個登錄功能,那還不簡單.
select * from user where acount=XX AND PWD =YY;
然而,某天,我們需要在登錄的時候更新最后登錄時間,也就是在user表里面加個lastLoginTime.
這時渣渣說
select id from user wher acount=XX AND PWD =YY;
update user set XXXX where id=@id;
然后就他就被打了.實際上,可以合為一句:
update user set XXXX where acount=XX AND PWD =YY;
受影響行數大於0表示登錄成功.
密碼的加密
某一天,渣渣看到某某數據庫被下載了.想到自己的數據庫如果泄露了怎么辦,上面全是明文.
於是他就開始想加密這個事.
1MD5(同一個密碼,MD5還是一樣的,所以否決了)
2des(用於可逆密碼加密解密,然而有些安全系數要求高的密碼是不能被解密的,所以有適用范圍)
3隨機加鹽HASH.
第三種方案,要加在原來的表加多一個salt列.
每一次的登錄的話,
應該是select * from user wher acount=XX
然后把里面的鹽加上輸入的密碼進行hash然后與數據庫的加密后pwd進行比較.比如string.equal(MD(pwd+salt),pwd)
true當然就是說輸入正確啦.
其實加密的方案我舉的都只是最簡單的例子.對於鹽的把握,大家開腦洞自己想吧.
(比如可以對原密碼進行md5,然后對於md5的每一個偶數位字符插入鹽的一個字符,從而最后組成一個不明所以的字符串)
"無驗證碼登錄"
我們為什么需要驗證碼呢?是為了防止被密碼爆破.
那么是否可以無驗證碼登錄呢?當然可以.
遍地開花的開放平台接口
比如支付寶,旺旺,qq,微博....
以qq為例.他的步驟無非就是,跳到一個指向自己應用的騰訊鏈接.用戶在那上面授權給這個應用.授權后返回token和openid給網站.
做過微信第三方服務器的人就知道,這個token是用來以授權者的名義做各種操作的,實際上,對於登錄,只要有這個openid,去數據庫檢索成功了,就應該視為登錄有效.所以token其實沒啥用.
可信狀態下不需要驗證碼
固定的上網行為
我們應該怎么定義什么樣的狀態算作穩定?比如,我單個用戶,連續10次的登錄有9次都是在同一個ip上登錄,那么這個ip的實際來源就應該視為可信的狀態.
固定的上網行為其實還有很多種,比如登錄的時間段眾數,登錄的頻率,習慣使用的瀏覽器.只要你想的周全了,其實有很多用戶細節可以捕捉.
不頻繁的操作
我們相信這個世界的人大體都是美好的,所以你一看到我這個網站,要登錄的時候我就需要你輸入驗證碼,但如果你連續數次密碼錯誤,那我就要懷疑你的人品,給你下套.具體的可以看新浪微博.
場景的變換
不明公共wifi
我們知道http 下,明文post上去的明文被截取了就能分分鍾拿到信息.所以https就登場了.我post的密文就算給你看了,你也解密不了,耶~
然而這並沒有卵用.https證書根據不同的級別收不同的錢.於是12306自己做了個證書,哈哈.如果你們有魄力教導用戶安裝自己做的證書,那就走這條路吧....
移動端(Android,iPhone)
這就是我最近遇到的一個比較蛋疼的問題.我們知道url有長度限制,並且參數還得(utf-8)編碼.所以合適的做法應該是可解密的json密文作為http請求報文的body傳上去.
然而.net環境的des默認工作模式是cbc,所以其他客戶端也應該用cbc進行加密/解析.java的默認實現不是cbc,而且它的偏移向量每次都隨機.所以加密的結果和c#不一樣.
這里引用s站的說法,中文意思就是java的des是ecb而.NET的是cdc.工作模式不一樣.並且,我發現一些java 的des實現是用隨機向量的,所以變量不一致的話結果也會和.net的不一樣.
SunJCE provider uses
ECB
as the default mode, andPKCS5Padding
as the default padding scheme for DES, DES-EDE and Blowfish ciphers. (JCA Doc)In
.Net
, The default operation mode for the symmetric algorithm isCipherMode.CBC
and default padding isPaddingMode.PKCS7
. (msdn..SymmetricAlgorithm)
/// <summary> /// DES加密字符串 /// </summary> /// <param name="encryptString">待加密的字符串</param> /// <param name="key"></param> /// <returns>加密成功返回加密后的字符串,失敗返回源串</returns> public static string DESEncrypt(this string encryptString, string key) { try { if (key.Length < 8) throw new ArgumentException("密鑰和向量必須為8位,否則加密解密都不成功", "key"); byte[] rgbKey = Encoding.UTF8.GetBytes(key.Substring(0, 8)); byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); using (var dCSP = new DESCryptoServiceProvider()) { using (MemoryStream mStream = new MemoryStream()) { using (var cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbKey), CryptoStreamMode.Write)) { cStream.Write(inputByteArray, 0, inputByteArray.Length); cStream.FlushFinalBlock(); cStream.Close(); return Convert.ToBase64String(mStream.ToArray()); } } } } catch { return encryptString; } }
這下面是哥用了2015年7月3日用了0.7個下午的時間研(抄)究(襲)出來的java加密代碼
import java.util.HashMap; import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.SecretKeySpec; import java.security.InvalidAlgorithmParameterException; import java.security.Key; import java.security.Security; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.IvParameterSpec; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.spec.KeySpec; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public static void main(String[] args) throws Exception { Map map = new HashMap(); map.put( "Msg", "哦" ); map.put( "Url", "oooo"); JSONObject jsonObject = JSONObject.fromObject( map ); String shit=jsonObject.toString(); System.out.println(shit); System.out.println(encrypt(shit,"UTF8")); } public static String encrypt(String message,String encoding,String myKey) throws Exception { Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); DESKeySpec desKeySpec = new DESKeySpec(myKey.getBytes(encoding)); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); IvParameterSpec iv = new IvParameterSpec(myKey.getBytes(encoding)); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); byte[] buf = cipher.doFinal(message.getBytes(encoding)); sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder(); String a = encoder.encode(buf); return a; }
這是哥移植到安卓的代碼,只是最后的base64用了Android的類,其余無區別
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import javax.crypto.spec.IvParameterSpec; public static String encrypt(String key, String message, String encoding) throws Exception { Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); DESKeySpec desKeySpec = new DESKeySpec(key.getBytes(encoding)); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); SecretKey secretKey = keyFactory.generateSecret(desKeySpec); IvParameterSpec iv = new IvParameterSpec(key.getBytes(encoding)); cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); byte[] buf = cipher.doFinal(message.getBytes(encoding));
return android.util.Base64.encodeToString(buf, android.util.Base64.DEFAULT);
}
ios(object c)的我不知道,但是同事告訴我,轉成的base64除了大小寫有細微的差別外,沒有其他問題(他們請求我的web api接口有正確的響應).
Android的,也許還能參考我的第一個參考鏈接.不過it does not work for me.
參考鏈接:
Android平台和java平台 DES加密解密互通程序及其不能互通的原因 .
C# and Java DES Encryption value are not identical
版權是我的,轉載沒有通過我的同意的爬蟲都是傻逼.