我是如何找回 Reddit 密码的

本文讲的是我是如何找回 Reddit 密码的,


我是如何找回 Reddit 密码的

黑掉整个星球,伙计们!

我真是一点自制力都没有。

好在我对这一点颇有自知之明。我有意识地筹划生活,所以尽管我跟海洛因上瘾的小白鼠一样不成熟,偶尔还是可以搞定一些事情。

嗯,简直是浪费时间!

我逛 Reddit 浪费了很多时间。如果我想拖延点事情的话,常常会开一个新标签页然后一头扎进 Reddit。但是有时我又得心无旁骛,减少干扰。比如 2015 年 —— 我专注于提升自己的编程水平,而在 Reddit 闲逛就成了负担。

我要搞个计划控制我自己。

于是我就想:让自己登陆不了账号咋样?

我是这样做的:

我给账号重设了随机密码。叫朋友在某天把密码用 email 发给我。这样就可以万无一失地让自己上不了 Reddit 啦。(出于周全的考虑我还修改了找回密码用的邮箱)。

本应有效,不过......

不幸的是,事实上朋友根本扛不住社会工程学。换句话说,他们「对你太好了」,如果你「求」他们要密码,他们还是会发给你。

不要这样子看我。

失败了几次后,我得找个更可靠的办法。谷歌搜索了一会儿,我发现了这个:

看上去不错。

完美!一个自动化且不需要朋友介入的方案!(我现在要疏远大部分朋友,所以这一点很重要。)

看上去并不完善,不过管他呢,有个办法就不错了。

我这样坚持了一阵子:在工作日把密码 email 给自己,周末收到密码,在互联网垃圾信息中浪费时间,待下周开始就再锁掉账号。我印象中这一套效果不错。

终于有一天写代码实在太忙了,我完全忘了这一回事。

一转就是两年后

我现在在 Airbnb 工作,薪酬颇丰。而且 Airbnb 刚巧有个巨大的测试组件。也就是说等待时间颇多,而等待就意味着可以上网摸鱼。

我决定讨回旧账号并找回 Reddit 密码。

哦,不。这可不好。

我不记得我做过这一切,不过我当时肯定是太生自己的气了,都把自己锁到了 2018 年之后了。我还把邮件内容隐藏了,所以除非等到邮件发出去,我根本看不到内容。

我该怎么办?只能新建一个 Reddit 账号然后从头开始吗?但是这样好麻烦啊。

我完全可以给 LetterMeLater 发邮件解释自己并不是真的想这么做。但是他们回信可能要好一会儿呢。而且你们都知道了,我是个急性子。这个网站看上去也不像是有客服团队的样子。更不要提写这种邮件有多尴尬了。我开始头脑风暴精心编造理由甚至扯到了去世的亲人,试图解释为什么需要看自己的邮件。

所有的选择都不怎么靠谱。那天晚上,从公司走到家一路上我都在思考自己的尴尬处境,突然就有灵感了。

搜索栏

我用手机打开浏览器 App 开始尝试:

嗯。

好吧。所以(邮件)标题肯定是有索引的。那(邮件)内容呢?

试了几个字母,果然没错。内容也是有索引的。记住:邮件内容里面有我的密码。

本质上这是一个执行子字符串检索的界面。通过在搜索栏输入字符串,搜索结果会告诉我密码中是否有我输入的子字符串。

万事俱备。

我赶回自己的公寓,放下包,取出笔记本电脑。

算法问题:已知函数 substring?(str),它会根据输入的密码是否包含任何已知的子字符串来返回 True 或 False。给定这个函数,写一个可以推导出隐含密码的算法。

算法

让我们好好想想。我记得我的密码有这些特征:随机字符组成的长字符串,就像这样子 asgoihej2409g。我很可能没有用任何大写字母(Reddit 并不要求密码中一定有大写字母),那么先假设我没用大写字母。如果我真用了大写字母,第一次尝试失败之后再将搜索范围扩大吧。

还有一个标题行,是检索的字符串的一部分。而且邮件标题就是 "password"。

假设密码长度为 6,就有了 6 个空位来放字符,有些字符会出现在标题行,有些不会。所以可以取出所有没有出现在标题行的字符,逐一尝试进行搜索,肯定可以碰到一个独一无二的字母,恰好出现在密码中。就像是命运之轮游戏。

继续逐个字母进行尝试,直到命中没有出现在标题行的字符。这样就找到了。

找到了第一个字母之后我仍然不知道它在字符串中的位置。不过我知道可以在它后面加一个不同的字符来构造一个更大的子字符串,直到再次命中。

有可能必需遍历字母表中每一个字符才能找到它。任何一个字母都可能是正确的,所以平均来说会命中中间位置的字母,如果字母表有 A 个字母,那么可以预计每个字母平均会落到 A/2 处(假设主题字母较少且没有超过两个字符的重复组合)。

继续构建子字符串,直到无法在末尾添加字符。

这还没完 — 不过接近了,我落下了字符串的前缀,因为我是随机选了个起点开始的。不过好办,只需要再重复一下之前的操作,方向反过来就好了。

搞定之后就可以着手重建密码了。总而言之,我需要搞定 L 个字符,每个字符平均需要猜测 A/2 次(A 是字母表长度),加起来需要猜测 A/2 * L 次。

准确地说,我还得再猜测 2A 次来确保字符串两端都到头了。所以总数是 A/2 * L + 2A,提取公因数就是 A(L/2 + 2).

假设密码中有 20 个字符,字母表由 a-z 和 0–9 组成,总长度为 36。所以总迭代次数是 36 * (20/2 + 2) = 36 * 12 = 432

可恶。

不过实际上是可行的。

生活中的编程

实现

首先:我得写一个客户端,用代码控制搜索框执行检索。也就是我的子字符串「先知」。这个网站显然没有 API,我得直接爬网站。

搜索用的 URL 模式看来就是简单的检索字符串,www.lettermelater.com/account.php?**qe=#{query_here}**。简单吧。

开始写脚本吧。我会用 Faraday 这个 gem 完成网络请求,交互简单,我比较熟悉。

首先写一个 API 类。

require 'faraday'

class Api
  BASE_URL = 'http://www.lettermelater.com/account.php'

  def self.get(query)
    Faraday.get(BASE_URL, qe: query)
  end
end

当然,我可没指望这就能用了,毕竟还没有授权脚本登陆我的账号。可以看到响应返回了 302 重定向,还在 cookie 中提供了错误信息。

[10] pry(main)> Api.get(“foo”)
=> #<Faraday::Response:0x007fc01a5716d8
...
{“date”=>”Tue, 04 Apr 2017 15:35:07 GMT”,
“server”=>”Apache”,
“x-powered-by”=>”PHP/5.2.17",
“set-cookie”=>”msg_error=You+must+be+signed+in+to+see+this+page.”,
“location”=>”.?pg=account.php”,
“content-length”=>”0",
“connection”=>”close”,
“content-type”=>”text/html; charset=utf-8"},
status=302>

那怎么登陆呢?显然得在 header 中带上 cookies。有了 Chrome 的 inspector 这简直轻而易举。

(我当然不会把真的 cookie 贴在这儿。有意思的是看上去 cookie 在客户端保存了 user_id,这是个好信号。)

反复排除之后我发现需要 code 和 user_id 才能通过验证…… 哎。

所以我把这些加到脚本中。(这只是个用作示例的假 cookie)


require 'faraday'

class Api
  BASE_URL = 'http://www.lettermelater.com/account.php'
  COOKIE = 'code=13fa1fa011169ab29007fcad17b2ae; user_id=279789'

  def self.get(query)
    Faraday.get(BASE_URL, { qe: query }, Cookie: COOKIE).body
  end
end
[29] pry(main)> Api.get(“foo”)
=> “\n<!DOCTYPE HTML PUBLIC \”-//W3C//DTD HTML 4.01//EN\” \”[http://www.w3.org/TR/html4/strict.dtd\](http://www.w3.org/TR/html4/strict.dtd%5C) (.markup--anchor .markup--pre-anchor data-href=markup--anchor markup--pre-anchor rel=markup--anchor markup--pre-anchor target=markup--anchor markup--pre-anchor)">\n<html>\n<head>\n\t<meta http-equiv=\”content-type\” content=\”text/html; charset=UTF-8\” />\n\t<meta name=\”Description\” content=\”LetterMeLater.com allows you to send emails to anyone, with the ability to have them sent at any future date and time you choose.\” />\n\t<meta name=\”keywords\” content=\”schedule email, recurring, repeating, delayed, text messaging, delivery, later, future, reminder, date, time, capsule\” />\n\t<title>LetterMeLater.com — Account Information</title>…

[30] pry(main)> _.include?(“Haseeb”)
=> true

拿到我的名字了,显然登陆成功了!

爬数据搞定了,现在需要解析爬到的数据。幸运的是,这并不难 — 如果页面中出现了 e-mail 就意味着搜索命中了,所以只需要找到这种情况下才会出现的字符串就好了。“password“ 在其他搜索失败的情况下并不会出现,所以就是它了。

def self.include?(substring)
  get(substring).include?(‘password’)
end

API 类弄完了。现在完全可以用 Ruby 实现子字符串检索了。

[31] pry(main)> Api.include?('password')
=> true
[32] pry(main)> Api.include?('f')
=> false
[33] pry(main)> Api.include?('g')
=> true

这个搞定之后就要用 stub 替换掉真正的 API 来琢磨算法了。发送 HTTP 请求会非常慢,还有可能在试验的时候被限流。假设 stub API 是正确的,一旦搞定了剩下的算法部分,只要换成真正的 API 可以用了。

下面就是内置了随机密码的 stub API 了:

class ApiStub
  SECRET_PASSWORD = 'g420hpfjpefoj490rjgsd'

  def self.include?(substring)
    SECRET_PASSWORD.include?(substring)
  end
end

在测试时用 stub API 注入到类中。万事俱备后再用真实的 API 来检索真正的密码。

下面就开始用 Apistub 类吧。先在较高的层次回忆一下算法流程,主要分为三步:

  1. 首先,找到第一个标题中没有,却在密码中出现的字母。拿它作起点。
  2. 向前构建字符串,直到字符串尾。
  3. 反向构建字符串,直到字符串头。

这样就搞定了!

先做准备工作。要注入 API,还要把当前的密码段置为空字符串。

class PasswordCracker
  def initialize(api)
    @api = api
    @password = ''
  end
end

接下来写三个方法,就按照刚才计划的做。

  def crack!
    find_starting_letter!
    build_forward!
    build_backward!
    @password
  end

完美。现在剩下的都可以在私有方法中执行。

为了找到第一个字母,需要遍历字母表中的每个字符,条件是没有出现在标题中。可以用 a-z 和 0-9 来构造字母表。用 Ruby 的范围运算符(..)可以轻松搞定:

ALPHABET = ((‘a’..’z’).to_a + (‘0’..’9').to_a).shuffle

我偏向于把字母表随机打乱这样可以避免密码中字母的分布造成的偏差。这种情况下算法找到每个字符平均需要检索 A/2 次,即使密码并不是随机分布的。

还可以把标题定义为一个常量。

SUBJECT = ‘password’

准备工作就是这些。下面该写 find_starting_letter 了。这需要遍历每个候选字母(按照字母表顺序,当然不能出现在标题中),直到第一个匹配。

  private

  def find_starting_letter!
    candidate_letters = ALPHABET - SUBJECT.chars
    @password = candidate_letters.find { |char| @api.include?(char) }
  end

在测试阶段看上去效果不错:

PasswordCracker.new(ApiStub).send(:find_starting_letter!) # => 'f'

下面是难点。

我会用递归来实现,因为结构更优雅。

def build_forward!
    puts "Current password: #{@password}"
    ALPHABET.each do |char|
      guess = @password + char

      if @api.include?(guess)
        @password = guess
        build_forward!
        # once I'm done building forward, jump out of all stack frames
        return
      end
    end
  end

上面的代码简洁明了。现在看看能不能和 stub API 工作。

[63] pry(main)> PasswordCracker.new(ApiStub).crack!
f
fj
fjp
fjpe
fjpef
fjpefo
fjpefoj
fjpefoj4
fjpefoj49
fjpefoj490
fjpefoj490r
fjpefoj490rj
fjpefoj490rjg
fjpefoj490rjgs
fjpefoj490rjgsd
=> “fjpefoj490rjgsd”

赞!有了后缀,现在需要反向构建字符串。代码应该看上去很相似。

def build_backward!
    puts "Current password: #{@password}"
    ALPHABET.each do |char|
      guess = char + @password

      if @api.include?(guess)
        @password = guess
        build_backward!
        return
      end
    end

实际上只有两行代码有异:如何构建 guess,以及递归调用的名字。可以重构一下。

def build!(forward:)
    puts "Current password: #{@password}"
    ALPHABET.each do |char|
      guess = forward ? @password + char : char + @password

      if @api.include?(guess)
        @password = guess
        build!(forward: forward)
        return
      end
    end
  end

现在另一个调用可以简化为:

  def build_forward!
    build!(forward: true)
  end

  def build_backward!
    build!(forward: false)
  end

来实战一下:

Apps-MacBook:password-recovery haseeb$ ruby letter_me_now.rb
Current password: 9
Current password: 90
Current password: 90r
Current password: 90rj
Current password: 90rjg
Current password: 90rjgs
Current password: 90rjgsd
Current password: 90rjgsd
Current password: 490rjgsd
Current password: j490rjgsd
Current password: oj490rjgsd
Current password: foj490rjgsd
Current password: efoj490rjgsd
Current password: pefoj490rjgsd
Current password: jpefoj490rjgsd
Current password: fjpefoj490rjgsd
Current password: pfjpefoj490rjgsd
Current password: hpfjpefoj490rjgsd
Current password: 0hpfjpefoj490rjgsd
Current password: 20hpfjpefoj490rjgsd
Current password: 420hpfjpefoj490rjgsd
Current password: g420hpfjpefoj490rjgsd
g420hpfjpefoj490rjgsd

漂亮。再加一些 print 语句和 log,PasswordCracker 就完成了。

require 'faraday'

class PasswordCracker
  ALPHABET = (('a'..'z').to_a + ('0'..'9').to_a).shuffle
  SUBJECT = 'password'

  def initialize(api)
    @api = api
    @password = ''
  end

  def crack!
    find_starting_letter!
    puts "Found first letter: #{@password}"
    puts "\nBuilding forward!\n"
    build_forward!
    puts "\nBuilding backward!\n"
    build_backward!
    puts "Done! The result is #{@password}."
    puts "We found it in #{@api.iterations} iterations"
    @password
  end

  private

  def find_starting_letter!
    candidate_letters = ALPHABET - SUBJECT.chars
    @password = candidate_letters.find { |char| @api.include?(char) }
  end

  def build_forward!
    build!(forward: true)
  end

  def build_backward!
    build!(forward: false)
  end

  def build!(forward:)
    puts "Current password: #{@password}"
    ALPHABET.each do |char|
      guess = forward ? @password + char : char + @password

      if @api.include?(guess)
        @password = guess
        build!(forward: forward)
        return
      end
    end
  end
end

class Api
  BASE_URL = 'http://www.lettermelater.com/account.php'
  COOKIE = 'code=13fa1fa011169ab29007fcad17b2ae; user_id=279789'
  @iterations = 0

  def self.get(query)
    @iterations += 1
    Faraday.get(BASE_URL, { qe: query }, Cookie: COOKIE).body
  end

  def self.include?(substring)
    get(substring).include?('password')
  end

  def self.iterations
    @iterations
  end
end

class ApiStub
  SECRET_PASSWORD = 'g420hpfjpefoj490rjgsd'
  @iterations = 0

  def self.include?(substring)
    @iterations += 1
    SECRET_PASSWORD.include?(substring)
  end

  def self.iterations
    @iterations
  end
end

接下来......就是见证奇迹的时刻。把 stub API 换成真实的 API,看看结果怎么样。

见证真相的时刻

上天保佑……

PasswordCracker.new(Api).crack!

(三倍速)

Boom. 443 次迭代。

赶紧去 Reddit 试了一下,成功登录。

哇噢。

真的有效。

回忆一下原来那个计算迭代数的公式:A(N/2 + 2)。真正的密码长度为 22,所以公式预计需要 36 * (22/2 + 2) = 36 * 13 = 468 次迭代。实际上用了 443 次迭代,所以估计值和观测值的误差在 5% 以内。

这就是数学。

什么鬼 鬼什么 什鬼么

真的有效

不用给客服写尴尬的邮件了。重获 Reddit 休闲时光。事实证明:编程——的确是——魔法。

(不过我又得找个新办法让自己暂时无法登录了。)

有了编程之技,我又可以在互联网上挥霍时间了。感谢阅读,如果喜欢请点赞!

—Haseeb






原文发布时间为:2017年5月10日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-09-17 04:11:12

我是如何找回 Reddit 密码的的相关文章

资金通过拍拍交易转账退款资金如何找回支付密码?

  若您账户资金是拍拍交易发生退款后,卖家通过即时转账退款给您,我们可以协助您通过验证账户资金的方式找回支付密码.请您联系我们提供以下资料: 1.支付这笔拍拍交易的银行卡图片(将实物卡拍照); 2.银行卡开卡人身份证图片(二代身份证需提供正反面); 3.网上银行支付这笔拍拍交易的银行订单记录截图(有订单号.支付金额.支付时间,查询方法可参考,或直接咨询银行客服).

destoon找回管理员密码的方法_php实例

很多人在使用 DESTOON 系统时,经常会出现忘记后台管理密码的情况,亦或者是管理密码被别盗了需要找回,这时候往往不知所措. 其实找回 DESTOON 系统登陆的密码很简单,我们只需要将保存在数据库里面的密码更改掉,换一个简单一点的密码,然后进入系统再更改成自己的就可以了. 如果系统开启了邮件发送,还可以通过前台的找回密码功能邮件找回. 可以使用数据库管理工具,例如PHPMyAdmin,找到destoon_member表, 修改管理员password字段值为: 14e1b600b1fd579f

怎么利用支付宝钱包找回支付密码

支持支付宝钱包7.6以上(含7.6)版本    前提条件:个人和公司账户都支持,账户已经绑定手机号码.   通过支付宝钱包找回支付密码共有以下4种方式:支付宝系统根据各账户的情况进行判断,各账户找回的方式不同. 请先查看您当前账户有哪几种找回方式,再选择最适合您的方式来找回即可. 一.短信找回:适用于手机号码可以使用. 1.登录支付宝钱包,点击[安全]--[账户安全]: 2.点击[找回支付密码],确认当前支付宝账户后,填写[登录密码],点击[确定]:   3.选择[通过短信+证件号码找回],系统

微信密码忘了怎么找回?微信密码找回方法

1.我们在手机中打开微信会进入到登录界面了,我们在登录界面点击下边的"忘记密码"链接. 2.然后在忘记密码打开界面会有三种找回微信密码方法"通过QQ号找回密码",此时就会通过浏览器打开"QQ安全中心" 这个大家可根据自己绑定的选择了.   3. 如果我点击QQ安全中心之后点击"通过密保验证"-->"密保手机" 再输入你手机号码.     4.输入手机号码之后点击旁边的"获取验证码"

找回路由器密码不得不知的步骤

  朋友最近居然把路由器的密码给忘记了,想重新设置密码需找回之前的密码才能使用.其实想要找回之前的密码最直接的办法便是恢复出厂设置,把密码恢复到默认,然后再登陆路由器管理界面进行设置.具体操作步骤如下: 第一步:检查路由器是不是正常工作,必须保持通电,查看路由器的指示灯就行,灯亮着就表示通电. 第二步:找到路由器的复位孔,也就是复位键,查看一下路由器的前后面板,找到旁有Reset单词的小孔,这个小孔就是路由器的复位孔. 第三步:用牙签或者圆珠笔芯.钢丝等尖物,插入复位孔,用力顶住小孔15秒左右,

ZblogPHP找回管理员密码的3种方法

方法一.使用工具快速找回 第一.下载重置密码文件  代码如下 复制代码 下载地址:http://soft.itbulu.com/zblog/zbp-passwd.zip   第二.安装和打开 下载文件,然后放到网站根目录中,然后我们地址打开这个文件. 这里我们看到上图页面,然后点击"登录"就可以直接登录后台,我们可以进入后台用户管理修改一个新的密码. 总结,这样我们可以快速且安全的重新设置ZBLOGPHP新密码. 方法三.自己写程序找回 把下面代码保存为find.php文件,并上传到网

wordpress后台如何找回登录密码?

说是登录后面再修改这个不现实了,找回密码更不可能后台管理员密码没有找回这个功能,那么我们按下面方法一解决. 1.方法一,利用phpmyadmin来修改 登录 phpMyAdmin ,找到WP数据库的wp_users表,在这里可以看到默认的用户admin,它的密码是加密过的,我们现在来设置它为123456吧,但这个123456的密文我也不知道了,好我们先来执行php md5加密码  代码如下 复制代码 echo md5('123456'); 输出结果 e10adc3949ba59abbe56e05

用U盘找回系统密码的小技巧

  设置了系统密码却忘记了,难道非要重装系统才行?不是的,现在给大家介绍一个小技巧,解决忘记密码给你带来的烦恼. 一.需要准备的工具 1.U盘一个 2.USBoot U盘启动制作软件 3.winRAR 压缩软件 需要注意的是,此方法是在文件格式为FAT32的状态下实现的. 二.原理 方法很简单,当你的机器出现登陆框,要求输入密码时,你按下windows键+U会出现辅助工具管理器. 运行辅助工具管理器 既然可以运行辅助工具,那就意味着可以运行放大镜.我们把放大镜程序换成其他的程序是不是也可以运行呢

如何找回mysql密码(linux/windows)

Like *UNIX 版本: 下面是错误答案: 首先停止MySQL服务,然后使用skip-grant-tables参数启动它:  代码如下 复制代码 shell> /etc/init.d/mysql stop shell> mysqld_safe --skip-grant-tables &此时无需授权就可以进入到MySQL命令行,使用SQL重置MySQL密码:UPDATE mysql.user SET Password=PASSWORD('...') WHERE User='...'