当前位置: 代码迷 >> 综合 >> 分享一套更安全的 API 用户登录 明文密码加密 设计方案 (适合用于非https的场景)
  详细解决方案

分享一套更安全的 API 用户登录 明文密码加密 设计方案 (适合用于非https的场景)

热度:2   发布时间:2023-12-10 17:48:48.0

 

1)早期登录接口一般都是采用 账号+明文密码 直接发送到服务端做校验,数据库存储的是用户密码 md5 值;

     此方法如果在没有用 https 的场景,很容易被抓包盗取用户密码;

 

2)另一种方法是用户密码在本地端使用 md5 转换后、再生成一个签名同时发送到服务端做校验;(常用于端对端的 API )

     此方法的好处是用户密码不在网络中流通,无需担心被抓包盗取用户密码;

     但有个重大的弊端就是、此时数据库中存储的 md5 转换后的用户密码就形同于明文存储了;

     如果此时数据库中存储的 md5 密码被盗取后、那将可以被直接模拟登录所有用户的帐户;

 

3)那么我们就希望能设计一个 防止密码被抓包盗取、又能确保数据库 md5 密码被盗取后 也不会影响用户帐户安全的解决方案;

     以下方案为个人研究后得出的总结,希望对大家有帮助吧!

     为了方便大家快速理解,所以就直接采用中文命名啦!

 

【双混淆加密存储设计方案】

· 数据库 - 存储:

    · DB密钥1 = md5("一层混淆A" + 明文密码)                                   //主要用于实现离线生成签名密钥,不在网络中传输可以有效防止劫持者仿造签名密钥
    · DB密钥2 = md5("二层混淆" + md5("一层混淆B" + 明文密码))    //主要用于校验用户端传来的加密密码,同时防止泄库导致密钥被盗取利用

    //签名密钥 = md5(md5("一层混淆A" + 明文密码) + md5("一层混淆B" + 明文密码))

· 用户端 - 登录:

    · 生成 缓存密钥 = md5("一层混淆A" + 明文密码)                         //同等于<DB密钥1>,不在网络中流通
    · 生成 传输密钥 = md5("一层混淆B" + 明文密码)                         //同等于<DB密钥2>的第一层
    · 生成 签名密钥 = md5(缓存密钥 + 传输密钥)                              //也不在网络中流通

· 服务端 - 接收:

    · 参数1 = 用户账号
    · 参数2 = 传输密钥

    · 生成<DB密钥2> = md5("二层混淆" + 参数2)

    · 提取<DB密钥1>    //通过<参数1>获取对应账户的<DB密钥1>
    · 提取<DB密钥2>    //通过<参数1>获取对应账户的<DB密钥2>

        > 验证生成的<DB密钥2>与提取的<DB密钥2>是否相同,如果相同则说明密码正确、这时才为用户创建访问令牌

    · 生成<签名密钥> = md5(提取的<DB密钥1> + 参数2)

· 总结:
    1)劫持者只能通过网络抓包提取到<传输密钥>,无法知道<明文密码>、也无法知道<签名密钥>
    2)如果数据库存储的<DB密钥1>与<DB密钥2>泄漏、也不影响账户的安全
          因为没有<明文密码>的协助就无法生成<传输密钥>、无法生成<传输密钥>就无法生成<签名密钥>

【附加 C# 代码模拟实现】

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApplication1
{using static global::ConsoleApplication1.Helper;class Program{static void Main(){var result_11 = DataBase.Register(用户账号: "user1", 明文密码: "123456");   //注册成功!var result_12 = DataBase.Register(用户账号: "user2", 明文密码: "111222");   //注册成功!var result_13 = DataBase.Register(用户账号: "user3", 明文密码: "333444");   //注册成功!var user1 = new Client();{var result1 = user1.Login(用户账号: "user1", 明文密码: "123456");       //登录成功!var result2 = user1.TestRequest();                                     //请求成功!}var user2 = new Client();{var result1 = user2.Login(用户账号: "user2", 明文密码: "123456");       //登录失败,密码不正确。var result2 = user2.TestRequest();                                     //请先登录后再操作。}Console.ReadKey();}}/// <summary>/// 模拟 - 数据库/// </summary>class DataBase{private static DataTable _userTable = Task.Run(() =>{var dt = new DataTable();dt.Columns.Add("用户账号");dt.Columns.Add("DB密钥1");dt.Columns.Add("DB密钥2");return dt;}).Result;/// <summary>/// 查看用户表数据/// </summary>public static DataTable View { get { return DataBase._userTable; } }/// <summary>/// 用户注册/// </summary>/// <param name="用户账号"></param>/// <param name="明文密码"></param>/// <returns></returns>public static Result<string> Register(string 用户账号, string 明文密码){if (string.IsNullOrWhiteSpace(用户账号))return new Result<string>(message: "请定义一个合法的用户账号。", data: null);if (string.IsNullOrWhiteSpace(明文密码))return new Result<string>(message: "请定义一个合法的明文密码。", data: null);lock (DataBase._userTable){var count = DataBase._userTable.AsEnumerable().Where(x => x["用户账号"].ToString() == 用户账号).Count();if (count >= 1)return new Result<string>(message: $"账号“{用户账号}”已存在。", data: null);var DB密钥1 = md5("一层混淆A" + 明文密码);var DB密钥2 = md5("二层混淆" + md5("一层混淆B" + 明文密码));var dr = DataBase._userTable.NewRow();dr["用户账号"] = 用户账号;dr["DB密钥1"] = DB密钥1;dr["DB密钥2"] = DB密钥2;DataBase._userTable.Rows.Add(dr);}return new Result<string>(message: "注册成功!", data: null);}/// <summary>/// 获取指定的用户信息/// </summary>/// <param name="用户账号"></param>/// <returns></returns>public static User GetUser(string 用户账号){var dr = default(DataRow);lock(DataBase._userTable)dr = DataBase._userTable.AsEnumerable().Where(x => x["用户账号"].ToString() == 用户账号).FirstOrDefault();if (dr == null)return null;var user = new User(用户账号: dr["用户账号"].ToString(),DB密钥1: dr["DB密钥1"].ToString(),DB密钥2: dr["DB密钥2"].ToString());return user;}public class User{public string 用户账号 { get; }public string DB密钥1 { get; }public string DB密钥2 { get; }public User(string 用户账号, string DB密钥1, string DB密钥2){this.用户账号 = 用户账号;this.DB密钥1 = DB密钥1;this.DB密钥2 = DB密钥2;}}}/// <summary>/// 模拟 - 服务端/// </summary>class Service{/// <summary>/// 所有用户登录信息集合/// </summary>private static List<LoginInfo> _loginInfoCollect = new List<LoginInfo>();/// <summary>/// 查看服务端的所有用户登录信息/// </summary>public static List<LoginInfo> View { get { return Service._loginInfoCollect; } }/// <summary>/// 模拟登录接口/// </summary>/// <param name="用户账号"></param>/// <param name="传输密钥"></param>/// <returns>登录成功后将返回一个token</returns>public static Result<string> Login(string 用户账号, string 传输密钥){if (string.IsNullOrWhiteSpace(用户账号))return new Result<string>(message: "请输入您的账号。", data: null);if (string.IsNullOrWhiteSpace(传输密钥))return new Result<string>(message: "密码不可为空。", data: null);var user = DataBase.GetUser(用户账号);if (user == null)return new Result<string>(message: "指定的账号不存在。", data: null);var DB密钥2 = md5("二层混淆" + 传输密钥);if (DB密钥2 != user.DB密钥2)return new Result<string>(message: "输入的密码不正确。", data: null);var 签名密钥 = md5(user.DB密钥1 + 传输密钥);var loginInfo = new LoginInfo(登录时间: DateTime.Now,用户账号: 用户账号,签名密钥: 签名密钥,token: Guid.NewGuid().ToString("n"));Service._loginInfoCollect.Add(loginInfo);return new Result<string>(message: "登录成功!", data: loginInfo.Token);}/// <summary>/// 模拟一个测试接口/// </summary>/// <param name="token"></param>/// <returns></returns>public static Result<string> TestRequest(string token){var loginInfo = Service._loginInfoCollect.Where(x => x.Token == token).FirstOrDefault();if (loginInfo == null)return new Result<string>(message: "登录信息已过期。", data: null);return new Result<string>(message: "请求成功!", data: $"当前登录账号为“{loginInfo.用户账号}”。");}}/// <summary>/// 模拟 - 用户端/// </summary>class Client{private LoginInfo _loginInfo;/// <summary>/// 查看当前用户端登录信息/// </summary>public LoginInfo View { get { return this._loginInfo; } }/// <summary>/// 模拟客户端登录/// </summary>/// <param name="用户账号"></param>/// <param name="明文密码"></param>/// <returns>登录成功返还可用的 token</returns>public Result<string> Login(string 用户账号, string 明文密码){var 缓存密钥 = md5("一层混淆A" + 明文密码);var 传输密钥 = md5("一层混淆B" + 明文密码);var 签名密钥 = md5(缓存密钥 + 传输密钥);//模拟请求服务端登录var result = Service.Login(用户账号, 传输密钥);if (result.Message == "登录成功!"){var loginInfo = new LoginInfo(登录时间: DateTime.Now,用户账号: 用户账号,签名密钥: 签名密钥,token: result.Data);this._loginInfo = loginInfo;return new Result<string>(message: $"登录成功!", data: loginInfo.Token);}return new Result<string>(message: $"登录失败。", data: result.Message);}/// <summary>/// 模拟消息请求/// </summary>/// <returns></returns>public Result<string> TestRequest(){if (this._loginInfo == null)throw new Exception("请先登录后再操作。");//模拟请求服务端接口return Service.TestRequest(token: this._loginInfo.Token);}}struct Result<TData>{public string Message { get; }public TData Data { get; }public Result(string message, TData data){this.Message = message;this.Data = data;}}class LoginInfo{public DateTime 登录时间 { get; }public string 用户账号 { get; }public string 签名密钥 { get; }public string Token { get; }public LoginInfo(DateTime 登录时间, string 用户账号, string 签名密钥, string token){this.登录时间 = 登录时间;this.用户账号 = 用户账号;this.签名密钥 = 签名密钥;this.Token = token;}}static class Helper{public static string md5(string plainText){if (plainText == null)return plainText;var textBytes = Encoding.UTF8.GetBytes(plainText);var md5Bytes = MD5.Create().ComputeHash(textBytes);var cipherText = BitConverter.ToString(md5Bytes).Replace("-", ""); //默认为大写return cipherText;}}
}

又凌晨一点钟该休息了,改天有空再慢慢完善哈!

 

  相关解决方案