欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-06-17 18:54:09
1)早期登录接口一般都是采用账号+明文密码 直接发送到服务端做校验,数据库存储的是用户密码 md5 值; 此方法如果在没有用 https 的场景,很容易被抓包盗取用户密码;2)另一种方法是用户密码在本地端使用 md5 转换后、再生成一个签名同时发送到服务端做校验;(常用于端对端的 API) 此方法的好处是用户密码完全不在网络中流通,无需担心被抓包盗取用户密码; 但有个重大的弊端就是、此时数据库中存储的 md5 转换后的用户密码就形同于明文存储了; ......

 

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;
        }
    }
}

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

 

本文地址:https://blog.csdn.net/rcr676/article/details/109634926