Skip to content

Commit 0bec484

Browse files
committed
crypto/openpgp: add s2k
s2k implements the string-to-key functions for OpenPGP R=rsc CC=golang-dev https://golang.org/cl/3937043
1 parent 6d94b14 commit 0bec484

File tree

3 files changed

+251
-0
lines changed

3 files changed

+251
-0
lines changed

src/pkg/crypto/openpgp/s2k/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright 2011 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
include ../../../../Make.inc
6+
7+
TARG=crypto/openpgp/s2k
8+
GOFILES=\
9+
s2k.go\
10+
11+
include ../../../../Make.pkg

src/pkg/crypto/openpgp/s2k/s2k.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright 2011 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// This package implements the various OpenPGP string-to-key transforms as
6+
// specified in RFC 4800 section 3.7.1.
7+
package s2k
8+
9+
import (
10+
"crypto/md5"
11+
"crypto/openpgp/error"
12+
"crypto/ripemd160"
13+
"crypto/sha1"
14+
"crypto/sha256"
15+
"crypto/sha512"
16+
"hash"
17+
"io"
18+
"os"
19+
)
20+
21+
// Simple writes to out the result of computing the Simple S2K function (RFC
22+
// 4880, section 3.7.1.1) using the given hash and input passphrase.
23+
func Simple(out []byte, h hash.Hash, in []byte) {
24+
Salted(out, h, in, nil)
25+
}
26+
27+
var zero [1]byte
28+
29+
// Salted writes to out the result of computing the Salted S2K function (RFC
30+
// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
31+
func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
32+
done := 0
33+
34+
for i := 0; done < len(out); i++ {
35+
h.Reset()
36+
for j := 0; j < i; j++ {
37+
h.Write(zero[:])
38+
}
39+
h.Write(salt)
40+
h.Write(in)
41+
n := copy(out[done:], h.Sum())
42+
done += n
43+
}
44+
}
45+
46+
// Iterated writes to out the result of computing the Iterated and Salted S2K
47+
// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
48+
// salt and iteration count.
49+
func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
50+
combined := make([]byte, len(in)+len(salt))
51+
copy(combined, salt)
52+
copy(combined[len(salt):], in)
53+
54+
if count < len(combined) {
55+
count = len(combined)
56+
}
57+
58+
done := 0
59+
for i := 0; done < len(out); i++ {
60+
h.Reset()
61+
for j := 0; j < i; j++ {
62+
h.Write(zero[:])
63+
}
64+
written := 0
65+
for written < count {
66+
if written+len(combined) > count {
67+
todo := count - written
68+
h.Write(combined[:todo])
69+
written = count
70+
} else {
71+
h.Write(combined)
72+
written += len(combined)
73+
}
74+
}
75+
n := copy(out[done:], h.Sum())
76+
done += n
77+
}
78+
}
79+
80+
// Parse reads a binary specification for a string-to-key transformation from r
81+
// and returns a function which performs that transform.
82+
func Parse(r io.Reader) (f func(out, in []byte), err os.Error) {
83+
var buf [9]byte
84+
85+
_, err = io.ReadFull(r, buf[:2])
86+
if err != nil {
87+
return
88+
}
89+
90+
h := hashFuncFromType(buf[1])
91+
if h == nil {
92+
return nil, error.UnsupportedError("hash for S2K function")
93+
}
94+
95+
switch buf[0] {
96+
case 1:
97+
f := func(out, in []byte) {
98+
Simple(out, h, in)
99+
}
100+
return f, nil
101+
case 2:
102+
_, err := io.ReadFull(r, buf[:8])
103+
if err != nil {
104+
return
105+
}
106+
f := func(out, in []byte) {
107+
Salted(out, h, in, buf[:8])
108+
}
109+
return f, nil
110+
case 3:
111+
_, err := io.ReadFull(r, buf[:9])
112+
if err != nil {
113+
return
114+
}
115+
count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
116+
f := func(out, in []byte) {
117+
Iterated(out, h, in, buf[:8], count)
118+
}
119+
return f, nil
120+
}
121+
122+
return nil, error.UnsupportedError("S2K function")
123+
}
124+
125+
// hashFuncFromType returns a hash.Hash which corresponds to the given hash
126+
// type byte. See RFC 4880, section 9.4.
127+
func hashFuncFromType(hashType byte) hash.Hash {
128+
switch hashType {
129+
case 1:
130+
return md5.New()
131+
case 2:
132+
return sha1.New()
133+
case 3:
134+
return ripemd160.New()
135+
case 8:
136+
return sha256.New()
137+
case 9:
138+
return sha512.New384()
139+
case 10:
140+
return sha512.New()
141+
case 11:
142+
return sha256.New224()
143+
}
144+
145+
return nil
146+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2011 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package s2k
6+
7+
import (
8+
"bytes"
9+
"crypto/sha1"
10+
"encoding/hex"
11+
"testing"
12+
)
13+
14+
var saltedTests = []struct {
15+
in, out string
16+
}{
17+
{"hello", "10295ac1"},
18+
{"world", "ac587a5e"},
19+
{"foo", "4dda8077"},
20+
{"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"},
21+
{"x", "f1d3f289"},
22+
{"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"},
23+
}
24+
25+
func TestSalted(t *testing.T) {
26+
h := sha1.New()
27+
salt := [4]byte{1, 2, 3, 4}
28+
29+
for i, test := range saltedTests {
30+
expected, _ := hex.DecodeString(test.out)
31+
out := make([]byte, len(expected))
32+
Salted(out, h, []byte(test.in), salt[:])
33+
if !bytes.Equal(expected, out) {
34+
t.Errorf("#%d, got: %x want: %x", i, out, expected)
35+
}
36+
}
37+
}
38+
39+
40+
var iteratedTests = []struct {
41+
in, out string
42+
}{
43+
{"hello", "83126105"},
44+
{"world", "6fa317f9"},
45+
{"foo", "8fbc35b9"},
46+
{"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"},
47+
{"x", "5a684dfe"},
48+
{"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"},
49+
}
50+
51+
func TestIterated(t *testing.T) {
52+
h := sha1.New()
53+
salt := [4]byte{4, 3, 2, 1}
54+
55+
for i, test := range iteratedTests {
56+
expected, _ := hex.DecodeString(test.out)
57+
out := make([]byte, len(expected))
58+
Iterated(out, h, []byte(test.in), salt[:], 31)
59+
if !bytes.Equal(expected, out) {
60+
t.Errorf("#%d, got: %x want: %x", i, out, expected)
61+
}
62+
}
63+
}
64+
65+
66+
var parseTests = []struct {
67+
spec, in, out string
68+
}{
69+
/* Simple with SHA1 */
70+
{"0102", "hello", "aaf4c61d"},
71+
/* Salted with SHA1 */
72+
{"02020102030405060708", "hello", "f4f7d67e"},
73+
/* Iterated with SHA1 */
74+
{"03020102030405060708f1", "hello", "f2a57b7c"},
75+
}
76+
77+
func TestParse(t *testing.T) {
78+
for i, test := range parseTests {
79+
spec, _ := hex.DecodeString(test.spec)
80+
buf := bytes.NewBuffer(spec)
81+
f, err := Parse(buf)
82+
if err != nil {
83+
t.Errorf("%d: Parse returned error: %s", i, err)
84+
continue
85+
}
86+
87+
expected, _ := hex.DecodeString(test.out)
88+
out := make([]byte, len(expected))
89+
f(out, []byte(test.in))
90+
if !bytes.Equal(out, expected) {
91+
t.Errorf("%d: output got: %x want: %x", i, out, expected)
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)