Ця курва
Тут покажемо як працює підпис ECDSA на рівні операцій над точкою,
що знаходиться на еліптичній кривій, та операціями додавання
та множення які рухать точку по цій кривій.
Точка на лінії
Для початку визнається точка на площині в координатах Якобі.
defmodule CA.Point do
defstruct [:x, :y, z: 0]
def isAtInfinity?(p) do p.y == 0 end
end
Та функція визначення належності точки до геометричного місця точок кривої (локуса).
defmodule CA.Curve do
@moduledoc false
require CA.Integer
require CA.Point
defstruct [:A, :B, :P, :N, :G, :name, :oid]
def contains?(curve, p) do
cond do
p.x < 0 || p.x > curve."P" - 1 -> false
p.y < 0 || p.y > curve."P" - 1 -> false
CA.Integer.ipow(p.y, 2) - (CA.Integer.ipow(p.x, 3)
+ curve."A" * p.x + curve."B")
|> CA.Integer.modulo(curve."P") != 0 -> false
true -> true
end
end
def getLength(curve) do
div(1 + String.length(Integer.to_string(curve."N", 16)), 2)
end
end
Множення в координатах Якобі
Алгоритм бінарного множення в координатах Якобі.
defmodule CA.Jacobian do
require CA.Integer
require CA.Point
@moduledoc false
def toJacobian(p), do: %CA.Point{x: p.x, y: p.y, z: 1}
def fromJacobian(p, cP) do
z = inv(p.z, cP)
%CA.Point{
x: CA.Integer.modulo(p.x * CA.Integer.ipow(z, 2), cP),
y: CA.Integer.modulo(p.y * CA.Integer.ipow(z, 3), cP)
}
end
def multiply(p, n, cN, cA, cP), do:
p |> toJacobian()
|> jacobianMultiply(n, cN, cA, cP)
|> fromJacobian(cP)
def add(p, q, cA, cP), do:
jacobianAdd(toJacobian(p), toJacobian(q), cA, cP)
|> fromJacobian(cP)
def inv(x, _) when x == 0, do: 0
def inv(x, n), do:
invOperator(1, 0, CA.Integer.modulo(x, n), n)
|> CA.Integer.modulo(n)
def invOperator(lm, hm, low, high) when low > 1 do
r = div(high, low)
invOperator(hm - lm * r, lm, high - low * r, low) end
def invOperator(lm, _, _, _), do: lm
def jacobianDouble(p, cA, cP) do
if p.y == 0 do
%CA.Point{x: 0, y: 0, z: 0}
else
ysq = CA.Integer.ipow(p.y, 2) |> CA.Integer.modulo(cP)
s = (4 * p.x * ysq) |> CA.Integer.modulo(cP)
m = (3 * CA.Integer.ipow(p.x, 2) + cA * CA.Integer.ipow(p.z, 4))
|> CA.Integer.modulo(cP)
nx = (CA.Integer.ipow(m, 2) - 2 * s) |> CA.Integer.modulo(cP)
ny = (m * (s - nx) - 8 * CA.Integer.ipow(ysq, 2))
|> CA.Integer.modulo(cP)
nz = (2 * p.y * p.z) |> CA.Integer.modulo(cP)
%CA.Point{x: nx, y: ny, z: nz}
end
end
def jacobianAdd(p, q, cA, cP) do
if p.y == 0 do
q
else
if q.y == 0 do
p
else
u1 = (p.x * CA.Integer.ipow(q.z, 2)) |> CA.Integer.modulo(cP)
u2 = (q.x * CA.Integer.ipow(p.z, 2)) |> CA.Integer.modulo(cP)
s1 = (p.y * CA.Integer.ipow(q.z, 3)) |> CA.Integer.modulo(cP)
s2 = (q.y * CA.Integer.ipow(p.z, 3)) |> CA.Integer.modulo(cP)
if u1 == u2 do
if s1 != s2 do
%CA.Point{x: 0, y: 0, z: 1}
else
jacobianDouble(p, cA, cP)
end
else
h = u2 - u1
r = s2 - s1
h2 = (h * h) |> CA.Integer.modulo(cP)
h3 = (h * h2) |> CA.Integer.modulo(cP)
u1h2 = (u1 * h2) |> CA.Integer.modulo(cP)
nx = (CA.Integer.ipow(r, 2) - h3 - 2 * u1h2) |> CA.Integer.modulo(cP)
ny = (r * (u1h2 - nx) - s1 * h3) |> CA.Integer.modulo(cP)
nz = (h * p.z * q.z) |> CA.Integer.modulo(cP)
%CA.Point{x: nx, y: ny, z: nz}
end
end
end
end
def jacobianMultiply(_p, n, _cN, _cA, _cP) when n == 0, do:
%CA.Point{x: 0, y: 0, z: 1}
def jacobianMultiply(p, n, _cN, _cA, _cP) when n == 1 do
case p.y do
0 -> %CA.Point{x: 0, y: 0, z: 1}
_ -> p
end
end
def jacobianMultiply(p, n, cN, cA, cP) when n < 0 or n >= cN do
case p.y do
0 -> %CA.Point{x: 0, y: 0, z: 1}
_ -> jacobianMultiply(p, CA.Integer.modulo(n, cN), cN, cA, cP)
end
end
def jacobianMultiply(p, _n, _cN, _cA, _cP) when p.y == 0, do:
%CA.Point{x: 0, y: 0, z: 1}
def jacobianMultiply(p, n, cN, cA, cP) when rem(n, 2) == 0 do
jacobianMultiply(p, div(n, 2), cN, cA, cP) |> jacobianDouble(cA, cP)
end
def jacobianMultiply(p, n, cN, cA, cP) do
jacobianMultiply(p, div(n, 2), cN, cA, cP)
|> jacobianDouble(cA, cP) |> jacobianAdd(p, cA, cP)
end
end
Параметри курв
Параметри кривих ідентифікуються атомом та/або OID,
та визначають точки А, Б, Засіювання, Точка на кривій,
Порядок, Кофактор.
defmodule CA.KnownCurves do
require CA.Curve
require CA.Point
@secp384r1 {1, 3, 132, 0, 34}
@secp256k1 {1, 3, 132, 0, 10}
@prime256v1 {1, 2, 840, 10045, 3, 1, 7}
@secp384r1name :secp384r1
@secp256k1name :secp256k1
@prime256v1name :prime256v1
def getCurveByOid(oid) do
case oid do
@secp256k1 -> secp256k1()
@secp384r1 -> secp384r1()
@prime256v1 -> prime256v1()
end
end
def getCurveByName(name) do
case name do
@secp256k1name -> secp256k1()
@secp384r1name -> secp384r1()
@prime256v1name -> prime256v1()
end
end
def secp256k1() do
%CA.Curve{
name: @secp256k1name,
A: 0x0000000000000000000000000000000000000000000000000000000000000000,
B: 0x0000000000000000000000000000000000000000000000000000000000000007,
P: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
N: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
G: %CA.Point{
x: 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
y: 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
},
oid: @secp256k1
}
end
def prime256v1() do
%CA.Curve{
name: @prime256v1name,
A: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC,
B: 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,
P: 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF,
N: 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,
G: %CA.Point{
x: 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
y: 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5
},
oid: @prime256v1
}
end
def secp384r1() do
%CA.Curve{
name: @secp384r1name,
A: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000fffffffc,
B: 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef,
P: 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff,
N: 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973,
G: %CA.Point{
x: 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
y: 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f
},
oid: @secp384r1
}
end
end
BigNum RND
Алгоритм генерація великого довільного числа,
яке береться як координата точки на еліптичній кривій,
автоматично визначається її пара, яка утворює точку на прямій,
і далі відбуваються подальші криптографічні перетворення.
defmodule CA.Integer do
@moduledoc false
import Bitwise
def modulo(x, n), do: rem(x, n) |> correctNegativeModulo(n)
def correctNegativeModulo(r, n) when r < 0, do: r + n
def correctNegativeModulo(r, _n), do: r
def ipow(base, p, acc \\ 1)
def ipow(base, p, acc) when p > 0, do: ipow(base, p - 1, base * acc)
def ipow(_base, _p, acc), do: acc
def between(minimum, maximum) when minimum < maximum do
range = maximum - minimum + 1
{bytesNeeded, mask} = calculateParameters(range)
randomNumber = :crypto.strong_rand_bytes(bytesNeeded)
|> :binary.bin_to_list()
|> bytesToNumber &&& mask
if randomNumber < range do
minimum + randomNumber
else
between(minimum, maximum)
end
end
def bytesToNumber(randomBytes, randomNumber \\ 0, i \\ 0)
def bytesToNumber([randomByte | otherRandomBytes], randomNumber, i), do:
bytesToNumber(otherRandomBytes, randomNumber ||| randomByte <<< (8 * i), i + 1)
def bytesToNumber([], randomNumber, _i), do: randomNumber
def calculateParameters(range), do: calculateParameters(range, 1, 0)
def calculateParameters(range, mask, bitsNeeded) when range > 0, do:
calculateParameters(range >>> 1, mask <<< 1 ||| 1, bitsNeeded + 1)
def calculateParameters(_range, mask, bitsNeeded), do:
{div(bitsNeeded, 8) + 1, mask}
end
ECDSA
Обчислення r та s для криптографічного підпису.
defmodule CA.ECDSA do
require CA.Point
require CA.Integer
require CA.Jacobian
@moduledoc "CA/ECDSA ECC Signature (SYNRC)."
def sign(message, privateKey, options) do
%{hashfunc: hashfunc} = Enum.into(options, %{hashfunc: :sha256})
number = :crypto.hash(hashfunc, message) |> numberFromString()
curve = CA.KnownCurves.secp384r1()
randNum = CA.Integer.between(1, curve."N" - 1)
r = CA.Jacobian.multiply(curve."G", randNum, curve."N", curve."A", curve."P").x
|> CA.Integer.modulo(curve."N")
s = ((number + r * privateKey) * CA.Jacobian.inv(randNum, curve."N"))
|> CA.Integer.modulo(curve."N")
{r, s}
end
def private(bin), do: numberFromString(:erlang.element(3,:erlang.element(2,X509.PrivateKey.from_pem(bin))))
def public(bin), do: :public_key.pem_entry_decode(hd(:public_key.pem_decode(bin)))
def numberFromString(string) do
Base.encode16(string)
|> Integer.parse(16)
|> (fn {parsedInt, ""} -> parsedInt end).()
end
def decodePointFromECPoint(ec) do
{{:ECPoint, raw}, {:namedCurve, oid}} = ec
bin = :binary.part(raw,1,:erlang.size(raw)-1)
curve = CA.KnownCurves.getCurveByOid(oid)
baseLength = CA.Curve.getLength(curve)
xs = :binary.part(bin, 0, baseLength)
ys = :binary.part(bin, baseLength, :erlang.size(bin) - baseLength)
%CA.Point{ x: numberFromString(xs), y: numberFromString(ys)}
end
def signature(name) do
{:ok, sig} = :file.read_file name
{{_,[{_,r},{_,s}]},""} = :asn1rt_nif.decode_ber_tlv sig
{ :ca_enroll.decode_integer(r),
:ca_enroll.decode_integer(s) }
end
def sign(file, key) do
{:ok, msg} = :file.read_file file
{:ok, pem} = :file.read_file key
sign(msg, private(pem), [])
end
def verify(file, signature_file, pub) do
{:ok, msg} = :file.read_file file
{:ok, pem} = :file.read_file pub
verify(msg, signature(signature_file), decodePointFromECPoint(public(pem)), [])
end
def verify(message, {r,s}, publicKey, options) do
%{hashfunc: hashfunc} = Enum.into(options, %{hashfunc: :sha256})
number = :crypto.hash(hashfunc, message) |> numberFromString()
curve = CA.KnownCurves.secp384r1()
inv = CA.Jacobian.inv(s, curve."N")
v = CA.Jacobian.add(
CA.Jacobian.multiply(curve."G", CA.Integer.modulo(number * inv, curve."N"),
curve."N", curve."A", curve."P"),
CA.Jacobian.multiply(publicKey, CA.Integer.modulo(r * inv, curve."N"),
curve."N", curve."A", curve."P" ), curve."A", curve."P")
cond do
r < 1 || r >= curve."N" -> false
s < 1 || s >= curve."N" -> false
CA.Point.isAtInfinity?(v) -> false
CA.Integer.modulo(v.x, curve."N") != r -> false
true -> true
end
end
end
Висновки
Промислової якості криптографічні перетворення з
використанням бібліотеки великих цілих чисел
GMP вбудованої в віртуальну машину Erlang
вміщаються в 250 LOC та всього в декілька разів
повільніше OpenSSL імплементації на мові C.
SYNRC ECC Sign/Verify Elixir
> CA.CSR.ca
> CA.CSR.csr "maxim"
> CA.CSR.client "maxim"
> CA.ECDSA.sign "mix.exs", "maxim.key"
> CA.ECDSA.OTP.sign "mix.exs", "maxim.key"
> CA.ECDSA.verify "mix.exs", "mix.sig", "maxim.pub"
> CA.ECDSA.OTP.verify "mix.exs", "mix.sig", "maxim.pub"
Для додаткової самоперевірки в середовищі Erlang/OTP ми скористалися стандартним
додатком CRYPTO. Так виглядає природній ECDSA підпис на платформі
Ericsson Open Telecom Platform, який імплементований в модулі CA.ECDSA.OTP:
def signBin(msg, priv) do
CA."ECPrivateKey"(privateKey: point, parameters: {:namedCurve, oid}) = priv
:crypto.sign(:ecdsa, :sha256, msg,
[point, :crypto.ec_curve(:pubkey_cert_records.namedCurves(oid))])
end
def verifyBin(msg, sig, pub) do
{CA."ECPoint"(point: point), {:namedCurve, oid}} = pub
:crypto.verify(:ecdsa, :sha256, msg, sig,
[point, :crypto.ec_curve(:pubkey_cert_records.namedCurves(oid))])
end
OpenSSL ECC Sign/Verify C
Для самоперевірки з найпоширенішими промисловими імплементаціями ECDSA ми
використали OpenSSL:
# export client=maxim
# openssl dgst -sha256 -sign $client.key mix.exs > mix.sig
# export client=maxim
# openssl x509 -pubkey -noout -in $client.pem > $client.pub
# openssl dgst -sha256 -verify $client.pub -signature mix.sig mix.exs
˙
˙