65.9K
CodeProject 正在变化。 阅读更多。
Home

用户讨厌密码(我们都是用户):再也不用记密码了

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (28投票s)

2017年8月25日

CPOL

16分钟阅读

viewsIcon

70057

downloadIcon

523

完整的源代码,可创建解决方案,让用户再也不用想、记或输入密码

更新信息

注意:如果您是第一次阅读本文,请跳至引言

2017年9月26日

密码要求不再令人沮丧

版本1.4.1.0(可在本文顶部和GitHub下载)中,我为C'YaPass*添加了一个有价值的、能缓解沮丧的选项。新功能将帮助您记住每个网站特定的密码要求。正如您所知,许多网站要求包含大写字母特殊字符和/或限制密码的长度。即使使用C'YaPass,这仍然可能非常令人沮丧,因为很难记住每个网站的要求。

*目前,它仅在WinForm应用程序中可用,但在接下来的几天里,我将把它添加到所有平台上。

现在,您可以将这些添加到SiteKey中,这样当您选择SiteKey时,它将为您自动设置这些项。以下是设置它时的快照

edit site requirements

然后,每次选择该站点密钥时,站点要求(在本例中为添加大写字母设置最大长度23)将为您设置。

site reqs selected automatically

这意味着您不再需要记住特定站点对密码的要求。

右键单击即可编辑先前添加的密钥

新功能允许您右键单击以编辑先前添加的密钥。而且,如果您在http://www.cyapass.com/page/get-c-yapass^获取新的安装程序包,它将无缝安装在旧版本上,并保留您所有先前添加的密钥。

引言

这一切都始于我2016年6月在这里CodeProject上发表的一篇文章

销毁所有密码:再也不用记密码了[^]

在那篇文章中,该文章已有超过30万次观看,我向读者介绍了一个想法,该想法允许用户创建极强的密码(SHA-256哈希),而用户只需要

  1. 画一个图案
  2. 选择一个站点

在那篇文章发表后,我不知疲倦地为大量的平台(Windows、iOS、Android和Web)编写应用程序,以确保用户无论在哪个平台运行,都能生成密码。

您将看到,可用性是必须解决的关键问题,因为密码不会存储在任何地方,而是每次都重新生成

all platforms

本文将

  1. 概述应用程序如何解决密码问题
  2. 引导您了解C# WinForm应用程序的代码
  3. 讨论此密码管理方法的优点
    1. 密码不会存储在任何地方(它们是每次生成的)
    2. 用户再也不用输入密码
    3. 用户再也不用记住密码
    4. 用户再也不用创建密码
  4. 揭示此方法的潜在弱点或攻击向量
  5. 进行RFC(征求意见稿)——什么能让应用程序更好?
  6. 宣布所有代码(所有平台)作为开源项目发布在我的GitHub地址。现在每个人都可以加入,使用各种应用程序,修改代码供自己使用,与(非开发者但)会受益于使用该应用程序的用户分享。
  7. 列出一些需要解决的挑战,以使软件更好。
  8. 本文最大的要点之一是让您思考密码,以及它们有多糟糕,以及必须采取措施。这是我尝试做__某事__的方式。

GitHub贡献

获取任何项目的开源代码,或加入并贡献。

*11/16/2017

系列中的下一篇文章

然后,在这篇主文章之后,我将为Android应用程序写一篇文章,为iOS应用程序写另一篇文章,以便我们可以看看每个平台上的具体挑战。

先试后建

如果您想尝试任何或所有版本的应用程序,您可以非常轻松地获取它们(而无需自己构建)。

所有链接都会将您带到一个着陆页,以便您决定是否要下载应用程序。当然,Web应用程序将在您的HTML5兼容浏览器中直接运行。

  1. App Store上的CYaPass[^]
  2. Google Play上的CYaPass[^]
  3. C'YaPass:忘记所有密码 | 获取C'YaPass WinForm[^]
  4. C'YaPass:忘记所有密码 | WebApp[^]

本文可能有趣的方面?

跨平台开发实战:挑战

让用户界面(UI)在所有平台上看起来相同是一个有趣的问题。

在所有平台上保持用户体验(UX)的一致性

用4种不同的语言/5种不同的技术编写应用程序

  1. Windows = C#
  2. Android = Java
  3. iOS = Swift
  4. Web = HTML5(JavaScript / CSS)
  5. Windows UWA = C# / WPF(Windows Presentation Foundation)

项目的核心挑战是

挑战
当用户选择相同的站点密钥并绘制相同的图案时,必须在每个平台上重新生成相同的密码值。

由于密码是每次生成的,并且不存储在任何地方,因此挑战在于算法必须在所有平台上重复,以便生成原始密码。也就是说,如果用户无法在每个平台上重现相同的密码,那么该软件将毫无用处,因为用户会发现他无法登录目标网站。

我相信您会发现,在跨平台软件中看到这一点实现是有趣的项目亮点。

C# 到 Java 的转换,比 C# 到 UWA(WPF)的转换更容易

我首先将原始应用程序编写为WinForm应用程序(因为我在那里拥有最多的经验,并且工具非常好)。当我将其转换为Android时,它出奇地容易,并且我能够在几天内用Java重写了该应用程序。

相比之下,转换为通用Windows应用程序(UWA)要困难得多,这就是为什么我还没有发布完整的官方版本。现在我将所有代码都开源了,我也会将不完整的UWA项目添加到GitHub存储库中。

真正的面向对象编程(OOP)的美妙与力量

最酷的事情之一是实际看到OOP(面向对象编程)如何帮助解决问题。当我们检查代码时,您将看到我在所有平台(iOS、Android、Windows、Web)上都实现了相同的域对象(类)。

真正的重用

例如,我在C#的WinForm应用程序中创建了一个名为Segment。我需要在Android应用程序中使用同一个类,但当然那段代码是用Java编写的。由于真正的OOP意味着程序被分解成组件,并且由于对象包含自己的数据和功能,因此我只需要将基本Segment对象的语法转换为Java语法即可。这适用于我所有的域对象。这就是真正的重用。

当然,您会在Swift、JavaScript、C#和Java中看到所有相同的类。这很有趣。

C'YaPass 如何尝试解决密码问题?

实际上,要谈论我们如何解决密码问题,我们首先需要谈谈密码问题是什么。实际上,密码问题是多方面的

  1. 密码必须随时(所有平台)可供其所有者使用——只要所有者需要登录目标网站
  2. 密码在任何时候都不能被除其所有者以外的任何人访问
  3. 密码不必由其所有者记住(知道)
  4. 密码不得存储在任何地方(不保存在文件、写在笔记本上等)
  5. 密码不必由其所有者输入
  6. 密码不必由其所有者创建
  7. 密码必须具有密码学强度

这并不是一个糟糕的需求列表,可以指导我们前进,并可用于衡量我在CYaPass应用程序中提供的解决方案的有效性。

应用程序如何帮助解决密码问题

这里需要一个类比,我恰好有一个。

类比:一个超级聪明的朋友,记忆力超群

想象一下,您有一个完全信任、记忆力超群的朋友。这位朋友能够记住无限数量的64个字符的字符串。他可以随时为您生成这些。

每当您访问新网站需要创建新密码时,您就给他打电话说:“嘿,你能给我一个只包含数字和字母的64个随机字符串,并记住我将用于我的First Bank密码吗?”

“当然,”您的朋友说,然后开始背诵:“7625b39b9ceaab94afd2b6b887d0577a79b68dcaade97df85bcf5e80de16c35c”。

现在,每次您需要密码时,只需给您信任的朋友打电话,然后登录您的First Bank账户。

无法访问

就是这么简单。最棒的是,您的密码没有写在任何地方,也没有保存在任何文件中。这意味着黑客无法访问它。

非常强大

您的密码也非常强大,因为它非常长。如果您在之前提到的Kaspersky网站上测试它,您会发现它会告诉您,破解它需要超过10000个世纪。

非常随机

它也完全是随机的,所以没有人能够轻易猜到,就像他们可能通过组合一堆单词一样。编写一个程序能够持续生成您朋友给您的相同随机字符串的可能性微乎其微。

可重现

它是完全可重现的(只要您的朋友活着并且可用),因为您所要做的就是给您的朋友打电话,他会再次告诉您密码。

您雇佣朋友的帮助是因为他可靠且乐于助人,并且允许您将这项工作委托给朋友。这让您不必再考虑密码。

这就是我们为用户创建此软件的原因——这样他们就不必再考虑密码了。

您的朋友有智慧

我们的软件实现只是略有不同,因为您的朋友有智慧,可以创建64个字符的随机字符串。我们必须在我们的系统中构建这种“智慧”。

C'YaPass 真正做什么

就像您的朋友一样,C'YaPass 真正做的是

  1. 在您需要新密码时,为您提供唯一的64个字符随机字符串
  2. 当您需要再次登录时,重新生成您原来的64个字符随机字符串

用户将在每个需要登录的网站上使用这些64个字符的随机字符串作为他们的密码。

这将对用户有价值的原因是,我们知道用户

  1. 创建弱密码
  2. 忘记密码
  3. 将密码存储在他人可访问的位置(电子表格、键盘底部的便利贴、笔记本等)

特殊字符、大写、长度

当然,因为大多数网站都不了解什么构成好密码(越长越强),它们经常创建糟糕的要求(添加特殊字符、大写等),所以CYaPass通过允许用户添加这些项来提供帮助。请记住,它设法在每个平台上的代码中做到这一点,因此用户的密码将始终匹配。

C'YaPass 减轻用户的负担

C'YaPass 将记忆和管理密码的负担从用户身上卸下。C'YaPass 与您朋友的区别在于,人类朋友有智慧,他用智慧来创建64个字符的随机字符串。在我们的情况下,我们需要构建某种算法,使C'YaPass能够为用户做到这一点。

大部分工作已经由SHA-256哈希算法为我们完成,我们将利用它。

哈希或单向加密

如果您阅读了我以前的文章,那么您就知道它是如何工作的,所以我在这里只做个总结。

要生成密码,用户必须

  1. 选择一个站点密钥(请记住,站点密钥是用户决定添加到列表中以记住他/她正在使用该密码的站点或登录名的任何字符串。这允许用户拥有多个站点密钥(每个登录名一个),同时使用一个图案。)
  2. 在网格上绘制一个图案,其中至少包含一个段(两点)

我们将使用以下站点密钥(supersite)和本图像中显示的图案

supersite example

应用程序生成前面图像中所示密码的方式如下

  1. 获取站点密钥并存储在字节缓冲区中
  2. 计算用户在网格上绘制的图案的值
  3. 将生成的值作为字节前置到站点密钥字节缓冲区(为站点密钥值加盐)
  4. 生成字节的SHA-256哈希——对于前面图像中显示的那个,我们的明文字符串看起来像“4088supersite
  5. 在文本框中显示密码(“4088supersite”的SHA-256哈希),供用户使用
  6. 将密码复制到用户的剪贴板以方便使用(可以粘贴到网站密码框中)。

您的思绪爆炸了吗?

这是否显得太简单了?您是否觉得应该有更多东西?一些读者在我的原始文章中,在仔细思考后简直惊呆了。“不可能这么简单,”他们喊道。“不可能!”

但是,请记住,应用程序所做的就是……

 
……以可重现的方式生成极长、密码学强度极高的密码。

任何SHA-256哈希都可以作为非常好的密码,因为它非常随机(密码学强度高),以至于不可能被猜测。

我们正在利用SHA-256哈希算法的强大功能。

这是Windows Form应用程序中的代码样子

private void ComputeHashBytes()
        {
            var m256 = SHA256Managed.Create();
            var patternBytes = 
                System.Text.Encoding.UTF8.GetBytes(us.PointValue.ToString());
            var selItemText = SiteListBox.SelectedItem.ToString();
            var siteBytes = System.Text.Encoding.UTF8.GetBytes(selItemText);
            byte[] allBytes = null;

            allBytes = new byte[patternBytes.Length + siteBytes.Length];
            patternBytes.CopyTo(allBytes, 0);
            siteBytes.CopyTo(allBytes, patternBytes.Length);

            byte[] hashBytes = m256.ComputeHash(allBytes);
            pwdBuilder = new StringBuilder();
            // create a string of hex values for output
            foreach (byte b in hashBytes) { pwdBuilder.AppendFormat("{0:x2}", b); }
        }

您可以看到,我只是使用了C#提供的Cryptography库来创建一个SHA256哈希。

唯一真正有趣的部分是us.PointValue.ToString()(我在前面的代码示例中将其加粗了)。

us部分是UserPath对象,它在用户单击网格上的每个帖子时管理PointValue的计算。我最初将其命名为UserShape,但后来更改了。正如我前面提到的,我为了尽快完成应用程序并使其可用而争分夺秒。

当您查看UserPath类时,您会发现它非常简单,但它做了一些工作,以确保用户永远不能画两次相同的段。例如,用户可以反复单击相同的两个帖子并更改值。相反,代码确保两个帖子之间的线只能出现一次。

 class UserPath
  {
    public List<Point> allPoints = new List<Point>();
    public HashSet<Segment> allSegments = new HashSet<Segment>();
    private Point currentPoint;
    public int PointValue;
    private int previousPostValue;

    public void append(Point currentPoint, int postValue)
    {
       this.currentPoint = currentPoint;
            
       if (allPoints.Count >= 1)
       {
           if (allPoints[allPoints.Count - 1].X == currentPoint.X && 
                         allPoints[allPoints.Count - 1].Y == currentPoint.Y)
           {
               // user clicked the same point twice
               return;
           }
           allSegments.Add(new Segment(allPoints[allPoints.Count - 1], 
                           currentPoint, postValue + previousPostValue));
       }
       allPoints.Add(currentPoint);
       previousPostValue = postValue;
    }

   public void CalculateGeometricValue()
   {
       this.PointValue = 0;
       foreach (Segment s in allSegments)
       {
          this.PointValue += s.PointValue;
         }
       }
   }
}

该类跟踪段,确保它们永远不会被添加两次,并以一种方式计算帖子值,即只有一种模式可以计算出特定值。您不能使用不同的路径计算出相同的值。这对于我们在生成用户最终哈希密码时添加到站点密钥的盐值的随机性很重要。

现在,为了好玩,让我们看看Swift代码(用于iPhone/iPad),我在这里重现了相同的功能。

class UserPath {
    
    var allPoints :[CGPoint]! = []
    var allSegments : Set<Segment> = Set<Segment>()
    var PostPoints : String!
    var PostValue : Int!
    var currentPoint : CGPoint = CGPoint.zero
    var previousPostValue : Int!
    
    init(){
        self.PostValue = 0
    }
    
    func append(currentPoint:CGPoint, postValue : Int) {
        self.currentPoint = currentPoint
        
        if allPoints.count >= 1{
            if (allPoints[allPoints.count-1].x == currentPoint.x && 
                          allPoints[allPoints.count - 1].y == currentPoint.y){
                return;
            }
            allSegments.insert(Segment(begin: allPoints[allPoints.count - 1], 
               end: currentPoint, pointValue: postValue + previousPostValue))
        }
        
        allPoints.append(currentPoint)
        previousPostValue = postValue;
    }
    
    func CalculateGeometricSaltValue(){
        self.PostValue = 0;
        for s in allSegments{
            self.PostValue = self.PostValue + s.PointValue
        }
    }
}

您可以看到两者非常相似,这使得在多个平台上生成应用程序变得更容易,因为我可以分别处理每个类,并在过程中进行测试。

仍需解决的挑战

重置密码

这是一个挑战,因为您需要旧密码和新密码。如果您通过更改绘制的图案来更改密码,您将不得不切换回原始绘制的图案以获取旧密码。然后,您必须绘制新图案来生成新密码。

共享站点密钥

允许用户在设备之间保存站点密钥,这样她就不必在每个设备上输入它们。

对于那些认为应用程序应该更安全的人,您可以

能够对站点密钥进行排序

允许用户按照她希望的方式(按字母顺序、最常用等)对站点密钥进行排序,并将顺序保存为首选项。

不允许用户添加重复的密钥

如果用户尝试输入重复的密钥,则只需突出显示该密钥。

WinForm剪贴板的奇怪间歇性错误

有一个间歇性错误,在将密码复制到剪贴板时会引发异常。不知道为什么会发生这种情况,但如果您忽略它并允许程序再次复制到剪贴板,那么它就会正常工作。它只在罕见的情况下发生,这就是为什么这个错误如此奇怪。这似乎是Windows有时会使剪贴板不可用。

Web应用程序:需要自动复制到用户剪贴板

高级讨论

人类可以创建无法破解的密码

不是很有趣吗,有些自创的密码可以非常强大——基本上是无法破解的?

您可以创建一个对您来说易于记忆的非常强大的密码。例如,如果您在Secure Password Checker — Kaspersky Lab[^]上检查以下密码$1im5h4dy1iv3s,它会告诉您需要800年才能暴力破解该密码。

所以,关键不是您自己无法创建强密码,而是您需要太多,以至于记不住所有密码变得很麻烦。此外,您不可避免地需要更改密码,然后您需要记住的更多(以及您需要忘记的旧密码)。

又一个盐:几乎无法破解

我们可以允许用户向她的每个设备添加一个只有她知道的秘密值(例如,382),这将增加一个额外的盐,她只需添加一次,但这将使得黑客无法猜测。因为会有第三个盐,这将使它几乎无法破解,因为会有太多的组合。

尝试代码,使用它,并加入GitHub和讨论

我希望您能尝试一下代码。获取应用程序或源代码并试用。我用它来管理我所有的密码,它非常棒,因为我再也不用考虑密码了。

为了方便起见,我在文章顶部包含了WinForm C#应用程序源代码,但它与通过GitHub提供的源代码相同。另外,是的,您会在其中找到一些不再使用的额外方法。这也是赶工完成应用程序的一部分。

关于应用程序的更多信息

我的网站上有更多关于该应用程序的信息:C'YaPass:忘记所有密码 [^]。

还有一些视频,您可以在其中观看它的实际操作。我认为最好的一个是我展示了它如何在Windows上使用,然后在iPhone上使用,它在我的“关于”页面上生成相同的密码:C'YaPass:忘记所有密码 | 关于[^]

历史

  • 2017年8月23日:本文首次发布,并正式宣布所有C'YaPass代码均可作为开源使用
© . All rights reserved.