为什么我们需要使用它?

互联网是一个极其危险的地方,有很多不怀好意的人想要访问我们的在线账户。通过使用双因素身份验证,可以为我们的账号提供额外的安全。用户名密码方式的登录变得越来越不安全,你肯定听说过“撞库”这个名词,是黑客圈的术语,即网络黑客将互联网上已泄露的账号密码,拿到其他网站批量登录,从而“撞出”其他网站的账号密码。很不幸的是,由于许多网民习惯多个网站使用一个账号密码,所以“撞库”有着不低的成功率。

对有些人来说,盗取密码比您想象的更简单

以下任意一种常见操作都可能让您面临密码被盗的风险:

  • 在多个网站上使用同一密码
  • 从互联网上下载软件
  • 点击电子邮件中的链接

    两步验证可以将别有用心的人阻挡在外,即使他们知道您的密码也无可奈何。

什么是Google两步验证?

借助Google两步验证,通过密码和手机为帐户提供双重保护



Q

第一步:您需要输入密码

每当您登录账户时,都需要照常输入账号密码。

第二步:还需要执行其他操作

接着,验证码将会以短信的形式发送到手机上或通过语音电话告知,或者通过Google Authenticator App生成提供。

多一道安全防线

大多数用户的帐户只有密码这一道安全防线。启用两步验证后,即使有人破解了您的密码,他们仍需要借助您的手机或安全密钥,才能登录您的帐户。

什么是Google Authenticator ?

Google Authenticator(Wiki)是谷歌推出的基于时间的动态口令app(谷歌身份验证),只需要在手机上安装该APP,就可以生成一个随着时间变化的一次性口令,解决大家的账户遭到恶意攻击的问题,在手机端生成动态口令后,除了用正常用户名和密码外,需要输入一次动态口令才能验证成功。

Google Authenticator采用的算法是TOTP(Time-Based One-Time Password基于时间的一次性密码),其核心内容包括以下三点:

  • 一个共享密钥(一个字节序列);
  • 当前时间输入;
  • 一个签名函数。

具体原理推荐大家阅读:

Google账户两步验证的工作原理

详解Google Authenticator工作原理

我在这里准备了一个完整可执行的C# WinForm程序,感兴趣的朋友请 点击这里 进行查看,提取码:hemd。

  • Account Name:对应我们的账号名,可以是手机号、邮箱等
  • Secret Key:这个是我们的密钥Key,用于生成密钥。一般我们将这个值存放在用户表中的某个字段中。
  • Encoded Key:这个是最终生成的密钥,用户如果无法扫码二维码,我们可以将密钥发送至用户手机。

上图中,可以看出有3个口令,即我们在代码中设置的漂移为30s,主要是防止出现如下问题:

  • 由于网络延时,用户输入延迟等因素,可能当服务器端接收到一次性密码时,T的数值已经改变,这样就会导致服务器计算的一次性密码值与用户输入的不同,验证失败。解决这个问题个一个方法是,服务器计算当前时间片以及前面的n个时间片内的一次性密码值,只要其中有一个与用户输入的密码相同,则验证通过。当然,n不能太大,否则会降低安全性。
  • 我们知道如果客户端和服务器的时钟有偏差,会造成与上面类似的问题,也就是客户端生成的密码和服务端生成的密码不一致。但是,如果服务器通过计算前n个时间片的密码并且成功验证之后,服务器就知道了客户端的时钟偏差。因此,下一次验证时,服务器就可以直接将偏差考虑在内进行计算,而不需要进行n次计算。

下面是C#源码提供:

public class TwoFactorAuthenticator
{
private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private TimeSpan DefaultClockDriftTolerance { get; set; } public TwoFactorAuthenticator()
{
DefaultClockDriftTolerance = TimeSpan.FromSeconds(30); //建议此处将时间漂移设置为30s,即允许前后各一个时间片。不能不建议设置太大,否则会降低安全性
} public TwoFactorAuthenticator(TimeSpan defaultClockDriftTolerance)
{
DefaultClockDriftTolerance = defaultClockDriftTolerance;
} /// <summary>
/// Generate a setup code for a Google Authenticator user to scan
/// </summary>
/// <param name="issuer">Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format </param>
/// <param name="accountName">Account Name (no spaces)</param>
/// <param name="accountSecretKey">Account Secret Key</param>
/// <param name="qrPixelsPerModule">Number of pixels per QR Module (2 pixels give ~ 100x100px QRCode)</param>
/// <returns>SetupCode object</returns>
public SetupCode GenerateSetupCode(string issuer, string accountName, string accountSecretKey, int qrPixelsPerModule)
{
var key = Encoding.UTF8.GetBytes(accountSecretKey);
return GenerateSetupCode(issuer, accountName, key, qrPixelsPerModule);
} /// <summary>
/// Generate a setup code for a Google Authenticator user to scan
/// </summary>
/// <param name="issuer">Issuer ID (the name of the system, i.e. 'MyApp'), can be omitted but not recommended https://github.com/google/google-authenticator/wiki/Key-Uri-Format </param>
/// <param name="accountName">Account Name (no spaces)</param>
/// <param name="accountSecretKey">Account Secret Key as byte[]</param>
/// <param name="qrPixelsPerModule">Number of pixels per QR Module (2 = ~120x120px QRCode)</param>
/// <returns>SetupCode object</returns>
public SetupCode GenerateSetupCode(string issuer, string accountName, byte[] accountSecretKey, int qrPixelsPerModule)
{
if (accountName == null) { throw new NullReferenceException("Account Title is null"); }
accountName = accountName.Trim();
var encodedSecretKey = Base32Encode(accountSecretKey);
var provisionUrl = string.IsNullOrWhiteSpace(issuer) ? $"otpauth://totp/{accountName}?secret={encodedSecretKey}" : string.Format("otpauth://totp/{2}:{0}?secret={1}&issuer={2}", accountName, encodedSecretKey, HttpUtility.UrlEncode(issuer, Encoding.UTF8));
using (var qrGenerator = new QRCodeGenerator())
using (var qrCodeData = qrGenerator.CreateQrCode(provisionUrl, QRCodeGenerator.ECCLevel.Q))
using (var qrCode = new QRCode(qrCodeData))
using (var qrCodeImage = qrCode.GetGraphic(qrPixelsPerModule))
using (var ms = new MemoryStream())
{
qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return new SetupCode(accountName, encodedSecretKey, Convert.ToBase64String(ms.ToArray()));
}
} public string GeneratePINAtInterval(string accountSecretKey, long counter, int digits = 6)
{
return GenerateHashedCode(accountSecretKey, counter, digits);
} internal string GenerateHashedCode(string secret, long iterationNumber, int digits = 6)
{
var key = Encoding.UTF8.GetBytes(secret);
return GenerateHashedCode(key, iterationNumber, digits);
} /// <summary>
///
/// </summary>
/// <param name="key"></param>
/// <param name="iterationNumber"></param>
/// <param name="digits">The digits parameter may have the values 6 or 8, and determines how long of a one-time passcode to display to the user. The default is 6.</param>
/// <returns></returns>
internal string GenerateHashedCode(byte[] key, long iterationNumber, int digits = 6)
{
var counter = BitConverter.GetBytes(iterationNumber); if (BitConverter.IsLittleEndian)
{
Array.Reverse(counter);
} var hmac = new HMACSHA1(key); var hash = hmac.ComputeHash(counter); var offset = hash[hash.Length - 1] & 0xf; // Convert the 4 bytes into an integer, ignoring the sign.
var binary =
((hash[offset] & 0x7f) << 24)
| (hash[offset + 1] << 16)
| (hash[offset + 2] << 8)
| (hash[offset + 3]); var password = binary % (int)Math.Pow(10, digits);
return password.ToString(new string('0', digits));
} private long GetCurrentCounter()
{
return GetCurrentCounter(DateTime.UtcNow, _epoch, 30);
} private long GetCurrentCounter(DateTime now, DateTime epoch, int timeStep)
{
return (long)(now - epoch).TotalSeconds / timeStep;
} public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient)
{
return ValidateTwoFactorPIN(accountSecretKey, twoFactorCodeFromClient, DefaultClockDriftTolerance);
} public bool ValidateTwoFactorPIN(string accountSecretKey, string twoFactorCodeFromClient, TimeSpan timeTolerance)
{
var codes = GetCurrentPINs(accountSecretKey, timeTolerance);
return codes.Any(c => c == twoFactorCodeFromClient);
} public string GetCurrentPIN(string accountSecretKey)
{
return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter());
} public string GetCurrentPIN(string accountSecretKey, DateTime now)
{
return GeneratePINAtInterval(accountSecretKey, GetCurrentCounter(now, _epoch, 30));
} public string[] GetCurrentPINs(string accountSecretKey)
{
return GetCurrentPINs(accountSecretKey, DefaultClockDriftTolerance);
} public string[] GetCurrentPINs(string accountSecretKey, TimeSpan timeTolerance)
{
var codes = new List<string>();
var iterationCounter = GetCurrentCounter();
var iterationOffset = 0; if (timeTolerance.TotalSeconds >= 30)
{
iterationOffset = Convert.ToInt32(timeTolerance.TotalSeconds / 30.00);
} var iterationStart = iterationCounter - iterationOffset;
var iterationEnd = iterationCounter + iterationOffset; for (var counter = iterationStart; counter <= iterationEnd; counter++)
{
codes.Add(GeneratePINAtInterval(accountSecretKey, counter));
} return codes.ToArray();
} private string Base32Encode(byte[] data)
{
const int inByteSize = 8;
const int outByteSize = 5;
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".ToCharArray(); int i = 0, index = 0;
var result = new StringBuilder((data.Length + 7) * inByteSize / outByteSize); while (i < data.Length)
{
var currentByte = data[i]; /* Is the current digit going to span a byte boundary? */
int digit;
if (index > (inByteSize - outByteSize))
{
var nextByte = (i + 1) < data.Length ? data[i + 1] : 0; digit = currentByte & (0xFF >> index);
index = (index + outByteSize) % inByteSize;
digit <<= index;
digit |= nextByte >> (inByteSize - index);
i++;
}
else
{
digit = (currentByte >> (inByteSize - (index + outByteSize))) & 0x1F;
index = (index + outByteSize) % inByteSize;
if (index == 0)
i++;
}
result.Append(alphabet[digit]);
} return result.ToString();
}
}

示例程序代码:

public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
} private void FrmMain_Load(object sender, EventArgs e)
{ } private void btnSetup_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtSecretKey.Text.Trim()) || string.IsNullOrEmpty(txtSecretKey.Text.Trim()) || string.IsNullOrEmpty(txtAccountName.Text.Trim())) return;
var tfA = new TwoFactorAuthenticator();
var setupCode = tfA.GenerateSetupCode(txtIssuer.Text.Trim(), txtAccountName.Text.Trim(), this.txtSecretKey.Text.Trim(), 3);
var ms = new MemoryStream(Convert.FromBase64String(setupCode.QrCodeSetupImageUrl));
this.pbQR.Image = Image.FromStream(ms);
ms.Dispose();
this.txtSetupCode.Text = $@"Account: {setupCode.Account}{System.Environment.NewLine}Secret Key: {this.txtSecretKey.Text.Trim()}{System.Environment.NewLine}Encoded Key: {setupCode.ManualEntryKey}";
} private void btnGetCurrentCode_Click(object sender, EventArgs e)
{
this.txtCurrentCodes.Text = string.Join(System.Environment.NewLine, new TwoFactorAuthenticator().GetCurrentPINs(this.txtSecretKey.Text));
} private void btnTest_Click(object sender, EventArgs e)
{
var tfA = new TwoFactorAuthenticator();
var result = tfA.ValidateTwoFactorPIN(txtSecretKey.Text, this.txtCode.Text);
MessageBox.Show(result ? "Validated" : "Incorrect", "Result");
}
}

使用Google两步验证的好处

  • 接入使用简单,门槛低,零成本
  • 保护系统账户安全
  • 节省企业成本(如短信、邮件需要额外费用)
  • 只需一部手机,同时管理多个账户

实际项目效果演示

欢迎与我讨论交流!

福禄·研发中心
福禄娃

你知道怎么使用Google两步验证保护账户安全吗?的更多相关文章

  1. 为Linux服务器的SSH登录启用Google两步验证

    对于Linux服务器而言使用密钥登录要比使用密码登录安全的多,毕竟当前网上存在多个脚本到处进行爆破. 这类脚本都是通过扫描IP端的开放端口并使用常见的密码进行登录尝试,因此修改端口号也是非常有必要的. ...

  2. Google两步验证安装使用方法

    http://www.williamlong.info/archives/2754.html

  3. Google 推出全新的两步验证机制

    近日 Google 在官方的 Apps Updates 博客公布了全新的两步验证功能--Google 提示,新的功能通过与 Google App 联动,进一步将验证确认工作缩减到仅有两步,同时支持 A ...

  4. 两步验证Authy时间同步问题

    Authy是我常用的软件之一,通常用于Google的两步验证,或者是其他基于Google两步验证的原理的衍生程序.比如Namesilo.印象笔记等均有使用. 先说说什么是两步验证. 两步验证 两步验证 ...

  5. 两步验证杀手锏:Java 接入 Google 身份验证器实战

    两步验证 大家应该对两步验证都熟悉吧?如苹果有自带的两步验证策略,防止用户账号密码被盗而锁定手机进行敲诈,这种例子屡见不鲜,所以苹果都建议大家开启两步验证的. Google 的身份验证器一般也是用于登 ...

  6. SecureCRT两步验证自动登录脚本

    简介 用于解决 Google Authenticator 的两步验证登录.涉及到密码,不建议脚本保存到公共环境. 安装oathtool Mac $ brew install oath-toolkit ...

  7. 使用KeePass管理两步验证

    目录 使用KeePass管理两步验证 两步验证 KeePass中管理两步验证 KeeTrayTOTP插件使用 使用KeePass管理两步验证 文:铁乐与猫 2018-9-9 KeePass 是一款管理 ...

  8. OPTAUTH 两步验证详解

    先贴图: 在对外网开放的后台管理系统中,使用静态口令进行身份验证可能会存在如下问题: (1) 为了便于记忆,用户多选择有特征作为密码,所有静态口令相比动态口令而言,容易被猜测和破解: (2) 黑客可以 ...

  9. 七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?

    一般情况下,点账户名——账户设置——安全设置,即可开通两步验证 具体步骤见链接  七牛云如何绑定二次验证码_虚拟MFA_两步验证_谷歌身份验证器?   二次验证码小程序(官网)对比谷歌身份验证器APP ...

随机推荐

  1. 在springboot pom文件配置过程,`spring-boot-maven-plugin`配置出错的问题解决及配置过程出现问题的一些思考

    在springboot pom文件配置过程,spring-boot-maven-plugin配置出错的问题解决及配置过程出现问题的一些思考 解决方法一: 也是最简单的方法,可能是maven没有来得及导 ...

  2. Java基础学习3

    Java语法学习3 基本运算符 关系运算符返回结果为布尔类型 %:取余 模运算 +.-.*./.% :二元运算符 两个变量间的运算 ++.-- 一元运算符 package Study; public ...

  3. POJ1321——棋盘问题

    http://poj.org/problem?id=1321 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解 ...

  4. 开源ASR服务器vosk

    概述 近几年由于AI的迅速发展,语音相关的自然语言处理NLP项目也变多了,新的技术也越来越成熟,其中TTS(语音生成)和ASR(语音识别)是NLP中非常重要的环节. 今天我们介绍一个开源的ASR项目v ...

  5. 记一次 .NET 某上市工业智造 CPU+内存+挂死 三高分析

    一:背景 1. 讲故事 上个月有位朋友加wx告知他的程序有挂死现象,询问如何进一步分析,截图如下: 看这位朋友还是有一定的分析基础,可能玩的少,缺乏一定的分析经验,当我简单分析之后,我发现这个dump ...

  6. Java基础系列(3)- HelloWorld详解

    HelloWorld 1.新建一个java文件 文件后缀名为.java Hello.java [注意点]系统可能没有显示文件后缀名,我们需要手动打开 2.编写代码 public class Hello ...

  7. js模块化开发 AMD CMD Commonjs

    在es6全面实行开来之前  js实现模块开发方案有: 1.AMD 异步模块开发定义  依赖前置,requireJs应用了这一规范 require([module], callback); 加载完后回调 ...

  8. LR虚拟用户已设置集合点,但controller无法设置集合点策略的解决方案

    原文来自:https://blog.csdn.net/qq_34982914/article/details/90905030 学习loadrunner的过程中,肯定涉及集合点的添加,但是我们按照书上 ...

  9. CF1251F-Red-White Fence【NTT】

    前言 刚开始看错题推了半天的生成函数 正题 题目链接:https://www.luogu.com.cn/problem/CF1251F 题目大意 $n$个白色木板,$k$个红色木板,给出这些木板的高度 ...

  10. python下载网-易-公-开-课的视频

    import requests url='http://v.stu.126.net/mooc-video/nos/mp4/2016/03/19/1004187130_5b0f0056936d4f78a ...