SJ's Here
SJ's Here

完全不实用的投票系统防刷票方案

前言

前段时间在为某个活动做准备的时候讨论到了投票的问题,而投票系统最大的问题就是如何防止刷票了。

一般能想到的最简单的方案就是记录用户的 IP 地址,限制每个 IP 地址只能投一次票。然而这是远远不够的,毕竟有些攻击者手里是有大量肉鸡的,成千上万个 IP 并不是难事。

受到区块链技术中工作量证明的启发,我们是不是能通过通过提高计算量来增加刷票的难度呢?

工作量证明

在比特币系统中,矿工需要找到一串数字,使得整个区块的内容加上这串数字的哈希值开头刚好有若干个 0 。因为哈希函数是不可逆函数(Trap-door function),因此矿工只能一个一个数字猜,而找出的这个数字便意味着矿工付出了一定的计算量。

在投票系统中,如果我们也采用类似的设计,要求每个客户投票时都需要付出一定的计算量,就能大大增加刷票的难度。其实并不能

投票系统中的 “工作量证明”

与加密货币的目的不同,我们的目的不是要建立去中心化的信任,而是要给投票行为增加一定的代价。

整个系统的逻辑不难构思。对于前端而言,在投票前从服务器请求一个特定的 Challenge String ,之后找出一个 Magic String 让 Challenge String 加上 MagicString 的哈希值结果开头有若干个 0 ,在投票时将 Magic String 一并发给服务器即可。对于服务器而言, Challenge String 可以是客户端的 IP 地址的加盐哈希值,在投票请求处理时验证客户端的 Magic String 是否合法也十分容易——只需要计算一次哈希值即可。

开始实践

最近正好在熟悉 Go 语言,于是就用 Go 来写了((

这里只贴出与主题相关的代码,其他的逻辑和本篇文章没有太大关系

后端:

func tellChallenge(w http.ResponseWriter, r *http.Request) {
	if !checkIp(getIp(r)) {
		w.WriteHeader(429)
		_, _ = fmt.Fprintf(w, "ip already voted")
		return
	}
	w.WriteHeader(200)
	log.Printf("Challenge request from %s\n", getIp(r))
	_, _ = fmt.Fprint(w, getChallenge(getIp(r)))
}

// challenge string 为客户端 ip 的加盐哈希
func getChallenge(ip net.IP) string {
	bytes := sha1.Sum([]byte(ip.String() + "you_know_what_this_is_the_salt_114514"))
	return hex.EncodeToString(bytes[:])
}

// 验证哈希结果
func checkChallenge(ip net.IP, magic string) bool {
	challenge := getChallenge(ip)
	bytes := []byte(challenge + magic)
	hash := sha256.Sum256(bytes[:])
	for i := 0; i < 3; i++ {
		if hash[i] != 0 {
			return false
		}
	}
	return (hash[3] & 0xE0) == 0
}

前端:

// 找到符合规则的 Magic String
function findMagic(challengeString) {
    if (challengeString !== null) {
        for (let i=0n; true; i++) {
            let result = sha256.array(challengeString + i.toString())
            if (result[0] === 0 && result[1] === 0 && result[2] === 0 && (result[3] & 0xE0) === 0) {
                return i.toString()
            }
        }
    }
    return null
}

完整的代码在 Gitea 上 ,部署好的服务地址在这里。读者有兴趣可以尝试一下投票(大概需要十分钟到一小时不等)。

总结

对于哈希结果开头 0 的个数要求,笔者其实试了很多次,最后才找到一个让前端耗时比较符合预期的。值得一提的是笔者发现,对结果开头的 0 的个数每增加一个,耗时结果似乎并不是原来的两倍。这个结果笔者目前还没有办法解释。

事实上,这种策略也并不能有效的阻止刷票——在有特定硬件的情况下找 MagicString 的速度会比普通的电脑快很多,因此有心人只需要付出一定的代价就可以继续刷票。

此外,这种策略让投票用户的体验下降了很多。总得来说,在投票中运用工作量证明并不是一个有效的手段。要求实用性的话,还是用 reCAPTCHA 吧((

没有标签
首页      未分类      完全不实用的投票系统防刷票方案

发表评论

textsms
account_circle
email

SJ's Here

完全不实用的投票系统防刷票方案
前言 前段时间在为某个活动做准备的时候讨论到了投票的问题,而投票系统最大的问题就是如何防止刷票了。 一般能想到的最简单的方案就是记录用户的 IP 地址,限制每个 IP 地址只能投…
扫描二维码继续阅读
2020-06-03