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

5 篇文章 0 订阅
2 篇文章 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;
        }
    }
}

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

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中常用的密码加密方法有MD5、SHA、AES等,下面分别介绍它们的使用方法。 MD5加密: MD5是一种不可逆的加密方式,将明文密码转换成一串固定长度的字符串,一般用于存储密码加密。Java中可以通过java.security.MessageDigest类实现MD5加密。示例代码如下: ```java import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5Util { public static String md5(String password) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bytes = md5.digest(password.getBytes()); StringBuilder stringBuilder = new StringBuilder(); for (byte b : bytes) { int temp = b & 0xff; String hexString = Integer.toHexString(temp); if (hexString.length() == 1) { stringBuilder.append("0").append(hexString); } else { stringBuilder.append(hexString); } } return stringBuilder.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { String password = "123456"; String encryptedPassword = md5(password); System.out.println(encryptedPassword); } } ``` SHA加密: SHA也是一种不可逆的加密方式,与MD5类似,但SHA安全。Java中可以通过java.security.MessageDigest类实现SHA加密。示例代码如下: ```java import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class SHAUtil { public static String sha(String password) { try { MessageDigest sha = MessageDigest.getInstance("SHA"); byte[] bytes = sha.digest(password.getBytes()); StringBuilder stringBuilder = new StringBuilder(); for (byte b : bytes) { int temp = b & 0xff; String hexString = Integer.toHexString(temp); if (hexString.length() == 1) { stringBuilder.append("0").append(hexString); } else { stringBuilder.append(hexString); } } return stringBuilder.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { String password = "123456"; String encryptedPassword = sha(password); System.out.println(encryptedPassword); } } ``` AES加密: AES是一种对称加密方式,即加密和解密使用相同的密钥。Java中可以通过javax.crypto.Cipher类实现AES加密。示例代码如下: ```java import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Base64; public class AESUtil { private static final String AES = "AES"; public static String encrypt(String content, String password) { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(password.getBytes()); keyGenerator.init(128, secureRandom); SecretKey secretKey = keyGenerator.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, AES); Cipher cipher = Cipher.getInstance(AES);// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(byteContent); return Base64.getEncoder().encodeToString(result);// 加密 } catch (Exception e) { e.printStackTrace(); } return null; } public static String decrypt(String content, String password) { try { KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(password.getBytes()); keyGenerator.init(128, secureRandom); SecretKey secretKey = keyGenerator.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, AES); Cipher cipher = Cipher.getInstance(AES);// 创建密码器 cipher.init(Cipher.DECRYPT_MODE, key);// 初始化 byte[] result = cipher.doFinal(Base64.getDecoder().decode(content)); return new String(result, "utf-8"); } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { String content = "123456"; String password = "password"; String encryptedContent = encrypt(content, password); System.out.println("加密后:" + encryptedContent); String decryptedContent = decrypt(encryptedContent, password); System.out.println("解密后:" + decryptedContent); } } ``` Spring Boot中可以使用Spring Security提供的PasswordEncoder接口来实现密码加密和解密。示例代码如下: ```java import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; public class PasswordUtil { public static String encode(String password) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(password); } public static boolean match(String password, String encodedPassword) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.matches(password, encodedPassword); } public static void main(String[] args) { String password = "123456"; String encodedPassword = encode(password); System.out.println("加密后:" + encodedPassword); boolean match = match(password, encodedPassword); System.out.println("匹配结果:" + match); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值