教程

使用以太坊和 Metamask 再也不需要输入密码

Mako Shan   |     |   2110 次阅读

我在 ConsenSys 为各种客户创建了很多概念性证明,通常他们希望利用以太坊的区块链解决一些业务问题。奇怪的是,这些系统经常被设计为具有标准的 Web 登录(即使用用户名和密码)。
之前我一直问自己为什么要这么设计,毕竟现在以太坊上可以解决输密码这件麻烦的事件。
于是我开始设计解决方案。

JWTs (JSON Web Tokens)

一个登陆标准网站或者 API 的常用规范。客户端向服务器发送密码请求登陆,服务器验证信息生成 Access_Token 响应给客户端,客户端接收 Token 储存,限制时间内每次请求服务器带上Access_Token。这里提供一个实现标准的 JWT 的教程。
JWTs 规范看起来不错,我开始思考其实在区块链认证身份非常容易,实际上当你使用以太坊,已经在不断的验证。
如果你想用以太坊地址作为登陆网站的账户,通过私钥签名一段数据很容易的认证你的身份,这段数据可以是服务器随机生成的任何字符串。因此,我们可以使用以太坊地址作为用户名并绕过对密码的需要。 实际上,我们甚至不需要使用区块链来做到这一点。
以下是使用 Express 举例:
首先,我们使用私钥做椭圆曲线签名:

var ethUtil = require(ethereumjs-util);var data = i am a string;
// Elliptic curve signature must be done on the Keccak256 Sha3 hash of a piece of data.
var hash = ethUtil.toBuffer(util.sha3(data));
var _sig = ethUtil.ecsign(hash, pkey);
var sig = {};
// The signature comes in three parts
sig.r = `0x${_sig.r.toString(hex)}`;
sig.s = `0x${_sig.s.toString(hex)}`;
sig.v = _sig.v.toString();

在这里有一些密码学知识,了解更多椭圆曲线签名的知识。 参考 Bitcoin wiki
总之,一旦拥有签名组件,我们可以将它们与用户的地址一起打包,发送到服务器获取身份授权。

POST /身份验证

var jwt = require(jsonwebtoken);
// Same data as before
var data = i am a string;
// Deconstruct signature
var r = utils.toBuffer(signature.r);
var s = utils.toBuffer(signature.s);
var v = utils.toBuffer(signature.v);
var m = utils.toBuffer(sha3(data));
// Recover the public key from a signature
var pub = utils.ecrecover(m, v, r, s);
var addr = 0x + utils.pubToAddress(pub).toString(hex)
// Check if it’s a match
var match = false;
if (addr == req.body.addr) { match = true; }
if (match) {
  // If the signature matches the owner supplied, create a
  // JSON web token for the owner that expires in 24 hours.
  var token = jwt.sign({user: req.body.addr}, i am another string,  { expiresIn: 1d });
  res.send(200, { success: 1, token: token })
} else {
  // If the signature doesn’t match, error out
  res.send(500, { err: Signature did not match.});
}

因此,只要给定一些数据,一个地址和一个椭圆曲线签名的组件,我们可以加密地证明该地址属于签署数据的人。 这很酷,不是吗。
如果对签名和地址的匹配感到满意,我们可以在服务器端为该地址签名一个 JWT。 在这种情况下,Token 有效期为1天。
现在我们只需要设计中间件来阻止任何路由能够访问服务或修改保护信息。

middleware/auth.js

function auth(req, res, next) {
  jwt.verify(req.body.token, i am another string, function(err, decoded) {
    if (err) { res.send(500, { error: Failed to authenticate token.}); }
    else {
      req.user = decoded.user;
      next();
    };
  });
}

app.js

// Routes
app.post(/UpdateData, auth, Routes.UpdateData);

如果提供的 token 符合发送请求的用户,我们将继续请求路由。 注意,中间件的修改请求。 我们需要引用 user 参数

POST /更新操作

function UpdateData(req, res) {
  // Only use the user that was set in req by auth middleware!
  var user = req.user;
  updateYourData(user, req.body.data);
  ...
}

已经存在信息! 您的用户已经登录,但不需要密码。

UI 操作

但是用户如何在浏览器中签名这些信息?这时候 Metamask 出现了, Metamask 是 Chrome 浏览器的插件,可以在浏览页面注入 web3。

mycomponent.jsx

makeSig(dispatch) {

 function toHex(s) {
   var hex = ‘’;
   for(var i=0;i<s.length;i++) { hex += ‘’+s.charCodeAt(i).toString(16); }
   return `0x${hex}`;
 }

 var data = toHex(‘i am a string’);
 web3.currentProvider.sendAsync({ id: 1, method: ‘personal_sign’, params: [web3.eth.accounts[0], data] },
 function(err, result) {
   let sig = result.result;
   let msg = ‘0x’ + web3.sha3(data);
   sig = sig.substr(2, sig.length);
   let r = ‘0x’ + sig.substr(0, 64);
   let s = ‘0x’ + sig.substr(64, 64);
   let v = parseInt(sig.substr(128, 2)) + 27
   let new_sig = { msg, r, s, v };
   dispatch({type: ‘UPDATE_SIG’, result: new_sig});
 })
}
render(){
  let { dispatch, _main: { sig } } = this.props;
  if (Object.keys(sig).length == 0) { this.makeSig(dispatch); }
  return (
   <p>I am a webpage</p>
  );
}

这将触发 Metamask 弹出一个对话框,要求用户输入密码签名消息:

一旦将签名信息保存在 reducer 中,就可以调用 API 进行身份验证。

最后

我不得不强调,使用 JWT 的每个Web应用都可以很容易利用以太坊的优势。 用户使用 Metamask 简单地方式绕过登录界面,这可比你当前用账户密码管理更安全。 这意味着忘记密码的用户减少,节约用户的时间,用户当然会高兴。
而且,这么做对假如您希望用户在没有中间人的情况下支付给对方,或者如果您想要连接以太坊的百万个其他功能等情况都适用。

参考:https://hackernoon.com/never-use-passwords-again-with-ethereum-and-metamask-b61c7e409f0d#.4ac4ifeb7
翻译:MakoShan

 
0 人喜欢