go.crypto: initial code

Manual edits to README.
Moved from main Go repository, deleted Makefiles, ran gofix -r go1rename.

Tested with: go test code.google.com/p/go.crypto/...

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5564059
diff --git a/README b/README
index bc954b0..f1e0cbf 100644
--- a/README
+++ b/README
@@ -1,16 +1,3 @@
-This is an empty Go subrepository.  To create a new subrepository,
-visit http://code.google.com/p/go/adminSource and under repositories,
-type the new name, check [x] Clone contents and select [empty] as
-the one you want to clone.
+This repository holds supplementary Go cryptography libraries.
 
-Then execute:
-	go get code.google.com/p/go.newrepo
-	cd $(go list -e -f '{{.Dir}}' code.google.com/p/go.newrepo)
-
-The go get will complain about not finding source code, but it will
-successfully check out the repository.
-
-Edit the README (this file) to describe the new subrepository, and then
-use the usual hg change, mail, submit to send in the change.
-You will need to follow http://golang.org/doc/contribute.html#Code_review
-to enable the Code Review extension (pointing into your Go root).
+To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/bcrypt/base64.go b/bcrypt/base64.go
new file mode 100644
index 0000000..fc31160
--- /dev/null
+++ b/bcrypt/base64.go
@@ -0,0 +1,35 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import "encoding/base64"
+
+const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+var bcEncoding = base64.NewEncoding(alphabet)
+
+func base64Encode(src []byte) []byte {
+	n := bcEncoding.EncodedLen(len(src))
+	dst := make([]byte, n)
+	bcEncoding.Encode(dst, src)
+	for dst[n-1] == '=' {
+		n--
+	}
+	return dst[:n]
+}
+
+func base64Decode(src []byte) ([]byte, error) {
+	numOfEquals := 4 - (len(src) % 4)
+	for i := 0; i < numOfEquals; i++ {
+		src = append(src, '=')
+	}
+
+	dst := make([]byte, bcEncoding.DecodedLen(len(src)))
+	n, err := bcEncoding.Decode(dst, src)
+	if err != nil {
+		return nil, err
+	}
+	return dst[:n], nil
+}
diff --git a/bcrypt/bcrypt.go b/bcrypt/bcrypt.go
new file mode 100644
index 0000000..d1ebee8
--- /dev/null
+++ b/bcrypt/bcrypt.go
@@ -0,0 +1,282 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
+// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
+package bcrypt
+
+// The code is a port of Provos and Mazières's C implementation. 
+import (
+	"code.google.com/p/go.crypto/blowfish"
+	"crypto/rand"
+	"crypto/subtle"
+	"errors"
+	"fmt"
+	"io"
+	"strconv"
+)
+
+const (
+	MinCost     int = 4  // the minimum allowable cost as passed in to GenerateFromPassword
+	MaxCost     int = 31 // the maximum allowable cost as passed in to GenerateFromPassword
+	DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
+)
+
+// The error returned from CompareHashAndPassword when a password and hash do
+// not match.
+var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
+
+// The error returned from CompareHashAndPassword when a hash is too short to
+// be a bcrypt hash.
+var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
+
+// The error returned from CompareHashAndPassword when a hash was created with
+// a bcrypt algorithm newer than this implementation.
+type HashVersionTooNewError byte
+
+func (hv HashVersionTooNewError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
+}
+
+// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
+type InvalidHashPrefixError byte
+
+func (ih InvalidHashPrefixError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
+}
+
+type InvalidCostError int
+
+func (ic InvalidCostError) Error() string {
+	return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost))
+}
+
+const (
+	majorVersion       = '2'
+	minorVersion       = 'a'
+	maxSaltSize        = 16
+	maxCryptedHashSize = 23
+	encodedSaltSize    = 22
+	encodedHashSize    = 31
+	minHashSize        = 59
+)
+
+// magicCipherData is an IV for the 64 Blowfish encryption calls in
+// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
+var magicCipherData = []byte{
+	0x4f, 0x72, 0x70, 0x68,
+	0x65, 0x61, 0x6e, 0x42,
+	0x65, 0x68, 0x6f, 0x6c,
+	0x64, 0x65, 0x72, 0x53,
+	0x63, 0x72, 0x79, 0x44,
+	0x6f, 0x75, 0x62, 0x74,
+}
+
+type hashed struct {
+	hash  []byte
+	salt  []byte
+	cost  uint32 // allowed range is MinCost to MaxCost
+	major byte
+	minor byte
+}
+
+// GenerateFromPassword returns the bcrypt hash of the password at the given
+// cost. If the cost given is less than MinCost, the cost will be set to
+// MinCost, instead. Use CompareHashAndPassword, as defined in this package,
+// to compare the returned hashed password with its cleartext version.
+func GenerateFromPassword(password []byte, cost int) ([]byte, error) {
+	p, err := newFromPassword(password, cost)
+	if err != nil {
+		return nil, err
+	}
+	return p.Hash(), nil
+}
+
+// CompareHashAndPassword compares a bcrypt hashed password with its possible
+// plaintext equivalent. Note: Using bytes.Equal for this job is
+// insecure. Returns nil on success, or an error on failure.
+func CompareHashAndPassword(hashedPassword, password []byte) error {
+	p, err := newFromHash(hashedPassword)
+	if err != nil {
+		return err
+	}
+
+	otherHash, err := bcrypt(password, p.cost, p.salt)
+	if err != nil {
+		return err
+	}
+
+	otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
+	if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
+		return nil
+	}
+
+	return ErrMismatchedHashAndPassword
+}
+
+func newFromPassword(password []byte, cost int) (*hashed, error) {
+	if cost < MinCost {
+		cost = DefaultCost
+	}
+	p := new(hashed)
+	p.major = majorVersion
+	p.minor = minorVersion
+
+	err := checkCost(cost)
+	if err != nil {
+		return nil, err
+	}
+	p.cost = uint32(cost)
+
+	unencodedSalt := make([]byte, maxSaltSize)
+	_, err = io.ReadFull(rand.Reader, unencodedSalt)
+	if err != nil {
+		return nil, err
+	}
+
+	p.salt = base64Encode(unencodedSalt)
+	hash, err := bcrypt(password, p.cost, p.salt)
+	if err != nil {
+		return nil, err
+	}
+	p.hash = hash
+	return p, err
+}
+
+func newFromHash(hashedSecret []byte) (*hashed, error) {
+	if len(hashedSecret) < minHashSize {
+		return nil, ErrHashTooShort
+	}
+	p := new(hashed)
+	n, err := p.decodeVersion(hashedSecret)
+	if err != nil {
+		return nil, err
+	}
+	hashedSecret = hashedSecret[n:]
+	n, err = p.decodeCost(hashedSecret)
+	if err != nil {
+		return nil, err
+	}
+	hashedSecret = hashedSecret[n:]
+
+	// The "+2" is here because we'll have to append at most 2 '=' to the salt
+	// when base64 decoding it in expensiveBlowfishSetup().
+	p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
+	copy(p.salt, hashedSecret[:encodedSaltSize])
+
+	hashedSecret = hashedSecret[encodedSaltSize:]
+	p.hash = make([]byte, len(hashedSecret))
+	copy(p.hash, hashedSecret)
+
+	return p, nil
+}
+
+func bcrypt(password []byte, cost uint32, salt []byte) ([]byte, error) {
+	cipherData := make([]byte, len(magicCipherData))
+	copy(cipherData, magicCipherData)
+
+	c, err := expensiveBlowfishSetup(password, cost, salt)
+	if err != nil {
+		return nil, err
+	}
+
+	for i := 0; i < 24; i += 8 {
+		for j := 0; j < 64; j++ {
+			c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
+		}
+	}
+
+	// Bug compatibility with C bcrypt implementations. We only encode 23 of
+	// the 24 bytes encrypted.
+	hsh := base64Encode(cipherData[:maxCryptedHashSize])
+	return hsh, nil
+}
+
+func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
+
+	csalt, err := base64Decode(salt)
+	if err != nil {
+		return nil, err
+	}
+
+	// Bug compatibility with C bcrypt implementations. They use the trailing
+	// NULL in the key string during expansion.
+	ckey := append(key, 0)
+
+	c, err := blowfish.NewSaltedCipher(ckey, csalt)
+	if err != nil {
+		return nil, err
+	}
+
+	rounds := 1 << cost
+	for i := 0; i < rounds; i++ {
+		blowfish.ExpandKey(ckey, c)
+		blowfish.ExpandKey(csalt, c)
+	}
+
+	return c, nil
+}
+
+func (p *hashed) Hash() []byte {
+	arr := make([]byte, 60)
+	arr[0] = '$'
+	arr[1] = p.major
+	n := 2
+	if p.minor != 0 {
+		arr[2] = p.minor
+		n = 3
+	}
+	arr[n] = '$'
+	n += 1
+	copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
+	n += 2
+	arr[n] = '$'
+	n += 1
+	copy(arr[n:], p.salt)
+	n += encodedSaltSize
+	copy(arr[n:], p.hash)
+	n += encodedHashSize
+	return arr[:n]
+}
+
+func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
+	if sbytes[0] != '$' {
+		return -1, InvalidHashPrefixError(sbytes[0])
+	}
+	if sbytes[1] > majorVersion {
+		return -1, HashVersionTooNewError(sbytes[1])
+	}
+	p.major = sbytes[1]
+	n := 3
+	if sbytes[2] != '$' {
+		p.minor = sbytes[2]
+		n++
+	}
+	return n, nil
+}
+
+// sbytes should begin where decodeVersion left off.
+func (p *hashed) decodeCost(sbytes []byte) (int, error) {
+	cost, err := strconv.Atoi(string(sbytes[0:2]))
+	if err != nil {
+		return -1, err
+	}
+	err = checkCost(cost)
+	if err != nil {
+		return -1, err
+	}
+	p.cost = uint32(cost)
+	return 3, nil
+}
+
+func (p *hashed) String() string {
+	return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
+}
+
+func checkCost(cost int) error {
+	if cost < MinCost || cost > MaxCost {
+		return InvalidCostError(cost)
+	}
+	return nil
+}
diff --git a/bcrypt/bcrypt_test.go b/bcrypt/bcrypt_test.go
new file mode 100644
index 0000000..9ad5c1c
--- /dev/null
+++ b/bcrypt/bcrypt_test.go
@@ -0,0 +1,194 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bcrypt
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestBcryptingIsEasy(t *testing.T) {
+	pass := []byte("mypassword")
+	hp, err := GenerateFromPassword(pass, 0)
+	if err != nil {
+		t.Fatalf("GenerateFromPassword error: %s", err)
+	}
+
+	if CompareHashAndPassword(hp, pass) != nil {
+		t.Errorf("%v should hash %s correctly", hp, pass)
+	}
+
+	notPass := "notthepass"
+	err = CompareHashAndPassword(hp, []byte(notPass))
+	if err != ErrMismatchedHashAndPassword {
+		t.Errorf("%v and %s should be mismatched", hp, notPass)
+	}
+}
+
+func TestBcryptingIsCorrect(t *testing.T) {
+	pass := []byte("allmine")
+	salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+	expectedHash := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+
+	hash, err := bcrypt(pass, 10, salt)
+	if err != nil {
+		t.Fatalf("bcrypt blew up: %v", err)
+	}
+	if !bytes.HasSuffix(expectedHash, hash) {
+		t.Errorf("%v should be the suffix of %v", hash, expectedHash)
+	}
+
+	h, err := newFromHash(expectedHash)
+	if err != nil {
+		t.Errorf("Unable to parse %s: %v", string(expectedHash), err)
+	}
+
+	// This is not the safe way to compare these hashes. We do this only for
+	// testing clarity. Use bcrypt.CompareHashAndPassword()
+	if err == nil && !bytes.Equal(expectedHash, h.Hash()) {
+		t.Errorf("Parsed hash %v should equal %v", h.Hash(), expectedHash)
+	}
+}
+
+func TestTooLongPasswordsWork(t *testing.T) {
+	salt := []byte("XajjQvNhvvRt5GSeFk1xFe")
+	// One byte over the usual 56 byte limit that blowfish has
+	tooLongPass := []byte("012345678901234567890123456789012345678901234567890123456")
+	tooLongExpected := []byte("$2a$10$XajjQvNhvvRt5GSeFk1xFe5l47dONXg781AmZtd869sO8zfsHuw7C")
+	hash, err := bcrypt(tooLongPass, 10, salt)
+	if err != nil {
+		t.Fatalf("bcrypt blew up on long password: %v", err)
+	}
+	if !bytes.HasSuffix(tooLongExpected, hash) {
+		t.Errorf("%v should be the suffix of %v", hash, tooLongExpected)
+	}
+}
+
+type InvalidHashTest struct {
+	err  error
+	hash []byte
+}
+
+var invalidTests = []InvalidHashTest{
+	{ErrHashTooShort, []byte("$2a$10$fooo")},
+	{ErrHashTooShort, []byte("$2a")},
+	{HashVersionTooNewError('3'), []byte("$3a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+	{InvalidHashPrefixError('%'), []byte("%2a$10$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+	{InvalidCostError(32), []byte("$2a$32$sssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")},
+}
+
+func TestInvalidHashErrors(t *testing.T) {
+	check := func(name string, expected, err error) {
+		if err == nil {
+			t.Errorf("%s: Should have returned an error", name)
+		}
+		if err != nil && err != expected {
+			t.Errorf("%s gave err %v but should have given %v", name, err, expected)
+		}
+	}
+	for _, iht := range invalidTests {
+		_, err := newFromHash(iht.hash)
+		check("newFromHash", iht.err, err)
+		err = CompareHashAndPassword(iht.hash, []byte("anything"))
+		check("CompareHashAndPassword", iht.err, err)
+	}
+}
+
+func TestUnpaddedBase64Encoding(t *testing.T) {
+	original := []byte{101, 201, 101, 75, 19, 227, 199, 20, 239, 236, 133, 32, 30, 109, 243, 30}
+	encodedOriginal := []byte("XajjQvNhvvRt5GSeFk1xFe")
+
+	encoded := base64Encode(original)
+
+	if !bytes.Equal(encodedOriginal, encoded) {
+		t.Errorf("Encoded %v should have equaled %v", encoded, encodedOriginal)
+	}
+
+	decoded, err := base64Decode(encodedOriginal)
+	if err != nil {
+		t.Fatalf("base64Decode blew up: %s", err)
+	}
+
+	if !bytes.Equal(decoded, original) {
+		t.Errorf("Decoded %v should have equaled %v", decoded, original)
+	}
+}
+
+func TestCost(t *testing.T) {
+	if testing.Short() {
+		return
+	}
+
+	pass := []byte("mypassword")
+
+	for c := 0; c < MinCost; c++ {
+		p, _ := newFromPassword(pass, c)
+		if p.cost != uint32(DefaultCost) {
+			t.Errorf("newFromPassword should default costs below %d to %d, but was %d", MinCost, DefaultCost, p.cost)
+		}
+	}
+
+	p, _ := newFromPassword(pass, 14)
+	if p.cost != 14 {
+		t.Errorf("newFromPassword should default cost to 14, but was %d", p.cost)
+	}
+
+	hp, _ := newFromHash(p.Hash())
+	if p.cost != hp.cost {
+		t.Errorf("newFromHash should maintain the cost at %d, but was %d", p.cost, hp.cost)
+	}
+
+	_, err := newFromPassword(pass, 32)
+	if err == nil {
+		t.Fatalf("newFromPassword: should return a cost error")
+	}
+	if err != InvalidCostError(32) {
+		t.Errorf("newFromPassword: should return cost error, got %#v", err)
+	}
+}
+
+func TestCostReturnsWithLeadingZeroes(t *testing.T) {
+	hp, _ := newFromPassword([]byte("abcdefgh"), 7)
+	cost := hp.Hash()[4:7]
+	expected := []byte("07$")
+
+	if !bytes.Equal(expected, cost) {
+		t.Errorf("single digit costs in hash should have leading zeros: was %v instead of %v", cost, expected)
+	}
+}
+
+func TestMinorNotRequired(t *testing.T) {
+	noMinorHash := []byte("$2$10$XajjQvNhvvRt5GSeFk1xFeyqRrsxkhBkUiQeg0dt.wU1qD4aFDcga")
+	h, err := newFromHash(noMinorHash)
+	if err != nil {
+		t.Fatalf("No minor hash blew up: %s", err)
+	}
+	if h.minor != 0 {
+		t.Errorf("Should leave minor version at 0, but was %d", h.minor)
+	}
+
+	if !bytes.Equal(noMinorHash, h.Hash()) {
+		t.Errorf("Should generate hash %v, but created %v", noMinorHash, h.Hash())
+	}
+}
+
+func BenchmarkEqual(b *testing.B) {
+	b.StopTimer()
+	passwd := []byte("somepasswordyoulike")
+	hash, _ := GenerateFromPassword(passwd, 10)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		CompareHashAndPassword(hash, passwd)
+	}
+}
+
+func BenchmarkGeneration(b *testing.B) {
+	b.StopTimer()
+	passwd := []byte("mylongpassword1234")
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		GenerateFromPassword(passwd, 10)
+	}
+}
diff --git a/blowfish/block.go b/blowfish/block.go
new file mode 100644
index 0000000..326292d
--- /dev/null
+++ b/blowfish/block.go
@@ -0,0 +1,192 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+// ExpandKey performs a key expansion on the given *Cipher. Specifically, it
+// performs the Blowfish algorithm's key schedule which sets up the *Cipher's
+// pi and substitution tables for calls to Encrypt. This is used, primarily,
+// by the bcrypt package to reuse the Blowfish key schedule during its
+// set up. It's unlikely that you need to use this directly.
+func ExpandKey(key []byte, c *Cipher) {
+	j := 0
+	for i := 0; i < 18; i++ {
+		var d uint32
+		for k := 0; k < 4; k++ {
+			d = d<<8 | uint32(key[j])&0x000000FF
+			j++
+			if j >= len(key) {
+				j = 0
+			}
+		}
+		c.p[i] ^= d
+	}
+
+	var l, r uint32
+	for i := 0; i < 18; i += 2 {
+		l, r = encryptBlock(l, r, c)
+		c.p[i], c.p[i+1] = l, r
+	}
+
+	for i := 0; i < 256; i += 2 {
+		l, r = encryptBlock(l, r, c)
+		c.s0[i], c.s0[i+1] = l, r
+	}
+	for i := 0; i < 256; i += 2 {
+		l, r = encryptBlock(l, r, c)
+		c.s1[i], c.s1[i+1] = l, r
+	}
+	for i := 0; i < 256; i += 2 {
+		l, r = encryptBlock(l, r, c)
+		c.s2[i], c.s2[i+1] = l, r
+	}
+	for i := 0; i < 256; i += 2 {
+		l, r = encryptBlock(l, r, c)
+		c.s3[i], c.s3[i+1] = l, r
+	}
+}
+
+// This is similar to ExpandKey, but folds the salt during the key
+// schedule. While ExpandKey is essentially expandKeyWithSalt with an all-zero
+// salt passed in, reusing ExpandKey turns out to be a place of inefficiency
+// and specializing it here is useful.
+func expandKeyWithSalt(key []byte, salt []byte, c *Cipher) {
+	j := 0
+	expandedKey := make([]uint32, 18)
+	for i := 0; i < 18; i++ {
+		var d uint32
+		for k := 0; k < 4; k++ {
+			d = d<<8 | uint32(key[j])&0x000000FF
+			j++
+			if j >= len(key) {
+				j = 0
+			}
+		}
+		expandedKey[i] = d
+		c.p[i] ^= d
+	}
+
+	j = 0
+	expandedSalt := make([]uint32, 18)
+	for i := 0; i < 18; i++ {
+		var d uint32
+		for k := 0; k < 4; k++ {
+			d = d<<8 | uint32(salt[j])&0x000000FF
+			j++
+			if j >= len(salt) {
+				j = 0
+			}
+		}
+		expandedSalt[i] = d
+	}
+
+	var l, r uint32
+	for i := 0; i < 18; i += 2 {
+		l ^= expandedSalt[i&2]
+		r ^= expandedSalt[(i&2)+1]
+		l, r = encryptBlock(l, r, c)
+		c.p[i], c.p[i+1] = l, r
+	}
+
+	for i := 0; i < 256; i += 4 {
+		l ^= expandedSalt[2]
+		r ^= expandedSalt[3]
+		l, r = encryptBlock(l, r, c)
+		c.s0[i], c.s0[i+1] = l, r
+
+		l ^= expandedSalt[0]
+		r ^= expandedSalt[1]
+		l, r = encryptBlock(l, r, c)
+		c.s0[i+2], c.s0[i+3] = l, r
+
+	}
+
+	for i := 0; i < 256; i += 4 {
+		l ^= expandedSalt[2]
+		r ^= expandedSalt[3]
+		l, r = encryptBlock(l, r, c)
+		c.s1[i], c.s1[i+1] = l, r
+
+		l ^= expandedSalt[0]
+		r ^= expandedSalt[1]
+		l, r = encryptBlock(l, r, c)
+		c.s1[i+2], c.s1[i+3] = l, r
+	}
+
+	for i := 0; i < 256; i += 4 {
+		l ^= expandedSalt[2]
+		r ^= expandedSalt[3]
+		l, r = encryptBlock(l, r, c)
+		c.s2[i], c.s2[i+1] = l, r
+
+		l ^= expandedSalt[0]
+		r ^= expandedSalt[1]
+		l, r = encryptBlock(l, r, c)
+		c.s2[i+2], c.s2[i+3] = l, r
+	}
+
+	for i := 0; i < 256; i += 4 {
+		l ^= expandedSalt[2]
+		r ^= expandedSalt[3]
+		l, r = encryptBlock(l, r, c)
+		c.s3[i], c.s3[i+1] = l, r
+
+		l ^= expandedSalt[0]
+		r ^= expandedSalt[1]
+		l, r = encryptBlock(l, r, c)
+		c.s3[i+2], c.s3[i+3] = l, r
+	}
+}
+
+func encryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+	xl, xr := l, r
+	xl ^= c.p[0]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[1]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[2]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[3]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[4]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[5]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[6]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[7]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[8]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[9]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[10]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[11]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[12]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[13]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[14]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[15]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[16]
+	xr ^= c.p[17]
+	return xr, xl
+}
+
+func decryptBlock(l, r uint32, c *Cipher) (uint32, uint32) {
+	xl, xr := l, r
+	xl ^= c.p[17]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[16]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[15]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[14]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[13]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[12]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[11]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[10]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[9]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[8]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[7]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[6]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[5]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[4]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[3]
+	xr ^= ((c.s0[byte(xl>>24)] + c.s1[byte(xl>>16)]) ^ c.s2[byte(xl>>8)]) + c.s3[byte(xl)] ^ c.p[2]
+	xl ^= ((c.s0[byte(xr>>24)] + c.s1[byte(xr>>16)]) ^ c.s2[byte(xr>>8)]) + c.s3[byte(xr)] ^ c.p[1]
+	xr ^= c.p[0]
+	return xr, xl
+}
+
+func zero(x []uint32) {
+	for i := range x {
+		x[i] = 0
+	}
+}
diff --git a/blowfish/blowfish_test.go b/blowfish/blowfish_test.go
new file mode 100644
index 0000000..1038d2e
--- /dev/null
+++ b/blowfish/blowfish_test.go
@@ -0,0 +1,210 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package blowfish
+
+import (
+	"testing"
+)
+
+type CryptTest struct {
+	key []byte
+	in  []byte
+	out []byte
+}
+
+// Test vector values are from http://www.schneier.com/code/vectors.txt.
+var encryptTests = []CryptTest{
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+	{
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0x51, 0x86, 0x6F, 0xD5, 0xB8, 0x5E, 0xCB, 0x8A}},
+	{
+		[]byte{0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
+		[]byte{0x7D, 0x85, 0x6F, 0x9A, 0x61, 0x30, 0x63, 0xF2}},
+	{
+		[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+		[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+		[]byte{0x24, 0x66, 0xDD, 0x87, 0x8B, 0x96, 0x3C, 0x9D}},
+
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+		[]byte{0x61, 0xF9, 0xC3, 0x80, 0x22, 0x81, 0xB0, 0x96}},
+	{
+		[]byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11},
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0x7D, 0x0C, 0xC6, 0x30, 0xAF, 0xDA, 0x1E, 0xC7}},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x4E, 0xF9, 0x97, 0x45, 0x61, 0x98, 0xDD, 0x78}},
+	{
+		[]byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0x0A, 0xCE, 0xAB, 0x0F, 0xC6, 0xA0, 0xA2, 0x8D}},
+	{
+		[]byte{0x7C, 0xA1, 0x10, 0x45, 0x4A, 0x1A, 0x6E, 0x57},
+		[]byte{0x01, 0xA1, 0xD6, 0xD0, 0x39, 0x77, 0x67, 0x42},
+		[]byte{0x59, 0xC6, 0x82, 0x45, 0xEB, 0x05, 0x28, 0x2B}},
+	{
+		[]byte{0x01, 0x31, 0xD9, 0x61, 0x9D, 0xC1, 0x37, 0x6E},
+		[]byte{0x5C, 0xD5, 0x4C, 0xA8, 0x3D, 0xEF, 0x57, 0xDA},
+		[]byte{0xB1, 0xB8, 0xCC, 0x0B, 0x25, 0x0F, 0x09, 0xA0}},
+	{
+		[]byte{0x07, 0xA1, 0x13, 0x3E, 0x4A, 0x0B, 0x26, 0x86},
+		[]byte{0x02, 0x48, 0xD4, 0x38, 0x06, 0xF6, 0x71, 0x72},
+		[]byte{0x17, 0x30, 0xE5, 0x77, 0x8B, 0xEA, 0x1D, 0xA4}},
+	{
+		[]byte{0x38, 0x49, 0x67, 0x4C, 0x26, 0x02, 0x31, 0x9E},
+		[]byte{0x51, 0x45, 0x4B, 0x58, 0x2D, 0xDF, 0x44, 0x0A},
+		[]byte{0xA2, 0x5E, 0x78, 0x56, 0xCF, 0x26, 0x51, 0xEB}},
+	{
+		[]byte{0x04, 0xB9, 0x15, 0xBA, 0x43, 0xFE, 0xB5, 0xB6},
+		[]byte{0x42, 0xFD, 0x44, 0x30, 0x59, 0x57, 0x7F, 0xA2},
+		[]byte{0x35, 0x38, 0x82, 0xB1, 0x09, 0xCE, 0x8F, 0x1A}},
+	{
+		[]byte{0x01, 0x13, 0xB9, 0x70, 0xFD, 0x34, 0xF2, 0xCE},
+		[]byte{0x05, 0x9B, 0x5E, 0x08, 0x51, 0xCF, 0x14, 0x3A},
+		[]byte{0x48, 0xF4, 0xD0, 0x88, 0x4C, 0x37, 0x99, 0x18}},
+	{
+		[]byte{0x01, 0x70, 0xF1, 0x75, 0x46, 0x8F, 0xB5, 0xE6},
+		[]byte{0x07, 0x56, 0xD8, 0xE0, 0x77, 0x47, 0x61, 0xD2},
+		[]byte{0x43, 0x21, 0x93, 0xB7, 0x89, 0x51, 0xFC, 0x98}},
+	{
+		[]byte{0x43, 0x29, 0x7F, 0xAD, 0x38, 0xE3, 0x73, 0xFE},
+		[]byte{0x76, 0x25, 0x14, 0xB8, 0x29, 0xBF, 0x48, 0x6A},
+		[]byte{0x13, 0xF0, 0x41, 0x54, 0xD6, 0x9D, 0x1A, 0xE5}},
+	{
+		[]byte{0x07, 0xA7, 0x13, 0x70, 0x45, 0xDA, 0x2A, 0x16},
+		[]byte{0x3B, 0xDD, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02},
+		[]byte{0x2E, 0xED, 0xDA, 0x93, 0xFF, 0xD3, 0x9C, 0x79}},
+	{
+		[]byte{0x04, 0x68, 0x91, 0x04, 0xC2, 0xFD, 0x3B, 0x2F},
+		[]byte{0x26, 0x95, 0x5F, 0x68, 0x35, 0xAF, 0x60, 0x9A},
+		[]byte{0xD8, 0x87, 0xE0, 0x39, 0x3C, 0x2D, 0xA6, 0xE3}},
+	{
+		[]byte{0x37, 0xD0, 0x6B, 0xB5, 0x16, 0xCB, 0x75, 0x46},
+		[]byte{0x16, 0x4D, 0x5E, 0x40, 0x4F, 0x27, 0x52, 0x32},
+		[]byte{0x5F, 0x99, 0xD0, 0x4F, 0x5B, 0x16, 0x39, 0x69}},
+	{
+		[]byte{0x1F, 0x08, 0x26, 0x0D, 0x1A, 0xC2, 0x46, 0x5E},
+		[]byte{0x6B, 0x05, 0x6E, 0x18, 0x75, 0x9F, 0x5C, 0xCA},
+		[]byte{0x4A, 0x05, 0x7A, 0x3B, 0x24, 0xD3, 0x97, 0x7B}},
+	{
+		[]byte{0x58, 0x40, 0x23, 0x64, 0x1A, 0xBA, 0x61, 0x76},
+		[]byte{0x00, 0x4B, 0xD6, 0xEF, 0x09, 0x17, 0x60, 0x62},
+		[]byte{0x45, 0x20, 0x31, 0xC1, 0xE4, 0xFA, 0xDA, 0x8E}},
+	{
+		[]byte{0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xB0, 0x07},
+		[]byte{0x48, 0x0D, 0x39, 0x00, 0x6E, 0xE7, 0x62, 0xF2},
+		[]byte{0x75, 0x55, 0xAE, 0x39, 0xF5, 0x9B, 0x87, 0xBD}},
+	{
+		[]byte{0x49, 0x79, 0x3E, 0xBC, 0x79, 0xB3, 0x25, 0x8F},
+		[]byte{0x43, 0x75, 0x40, 0xC8, 0x69, 0x8F, 0x3C, 0xFA},
+		[]byte{0x53, 0xC5, 0x5F, 0x9C, 0xB4, 0x9F, 0xC0, 0x19}},
+	{
+		[]byte{0x4F, 0xB0, 0x5E, 0x15, 0x15, 0xAB, 0x73, 0xA7},
+		[]byte{0x07, 0x2D, 0x43, 0xA0, 0x77, 0x07, 0x52, 0x92},
+		[]byte{0x7A, 0x8E, 0x7B, 0xFA, 0x93, 0x7E, 0x89, 0xA3}},
+	{
+		[]byte{0x49, 0xE9, 0x5D, 0x6D, 0x4C, 0xA2, 0x29, 0xBF},
+		[]byte{0x02, 0xFE, 0x55, 0x77, 0x81, 0x17, 0xF1, 0x2A},
+		[]byte{0xCF, 0x9C, 0x5D, 0x7A, 0x49, 0x86, 0xAD, 0xB5}},
+	{
+		[]byte{0x01, 0x83, 0x10, 0xDC, 0x40, 0x9B, 0x26, 0xD6},
+		[]byte{0x1D, 0x9D, 0x5C, 0x50, 0x18, 0xF7, 0x28, 0xC2},
+		[]byte{0xD1, 0xAB, 0xB2, 0x90, 0x65, 0x8B, 0xC7, 0x78}},
+	{
+		[]byte{0x1C, 0x58, 0x7F, 0x1C, 0x13, 0x92, 0x4F, 0xEF},
+		[]byte{0x30, 0x55, 0x32, 0x28, 0x6D, 0x6F, 0x29, 0x5A},
+		[]byte{0x55, 0xCB, 0x37, 0x74, 0xD1, 0x3E, 0xF2, 0x01}},
+	{
+		[]byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01},
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0xFA, 0x34, 0xEC, 0x48, 0x47, 0xB2, 0x68, 0xB2}},
+	{
+		[]byte{0x1F, 0x1F, 0x1F, 0x1F, 0x0E, 0x0E, 0x0E, 0x0E},
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0xA7, 0x90, 0x79, 0x51, 0x08, 0xEA, 0x3C, 0xAE}},
+	{
+		[]byte{0xE0, 0xFE, 0xE0, 0xFE, 0xF1, 0xFE, 0xF1, 0xFE},
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0xC3, 0x9E, 0x07, 0x2D, 0x9F, 0xAC, 0x63, 0x1D}},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0x01, 0x49, 0x33, 0xE0, 0xCD, 0xAF, 0xF6, 0xE4}},
+	{
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0xF2, 0x1E, 0x9A, 0x77, 0xB7, 0x1C, 0x49, 0xBC}},
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x24, 0x59, 0x46, 0x88, 0x57, 0x54, 0x36, 0x9A}},
+	{
+		[]byte{0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},
+		[]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
+		[]byte{0x6B, 0x5C, 0x5A, 0x9C, 0x5D, 0x9E, 0x0A, 0x5A}},
+}
+
+func TestCipherEncrypt(t *testing.T) {
+	for i, tt := range encryptTests {
+		c, err := NewCipher(tt.key)
+		if err != nil {
+			t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+			continue
+		}
+		ct := make([]byte, len(tt.out))
+		c.Encrypt(ct, tt.in)
+		for j, v := range ct {
+			if v != tt.out[j] {
+				t.Errorf("Cipher.Encrypt, test vector #%d: cipher-text[%d] = %#x, expected %#x", i, j, v, tt.out[j])
+				break
+			}
+		}
+	}
+}
+
+func TestCipherDecrypt(t *testing.T) {
+	for i, tt := range encryptTests {
+		c, err := NewCipher(tt.key)
+		if err != nil {
+			t.Errorf("NewCipher(%d bytes) = %s", len(tt.key), err)
+			continue
+		}
+		pt := make([]byte, len(tt.in))
+		c.Decrypt(pt, tt.out)
+		for j, v := range pt {
+			if v != tt.in[j] {
+				t.Errorf("Cipher.Decrypt, test vector #%d: plain-text[%d] = %#x, expected %#x", i, j, v, tt.in[j])
+				break
+			}
+		}
+	}
+}
+
+func TestSaltedCipherKeyLength(t *testing.T) {
+	var key []byte
+	for i := 0; i < 4; i++ {
+		_, err := NewSaltedCipher(key, []byte{'a'})
+		if err != KeySizeError(i) {
+			t.Errorf("NewSaltedCipher with short key, gave error %#v, expected %#v", err, KeySizeError(i))
+		}
+		key = append(key, 'a')
+	}
+
+	// A 57-byte key. One over the typical blowfish restriction.
+	key = []byte("012345678901234567890123456789012345678901234567890123456")
+	_, err := NewSaltedCipher(key, []byte{'a'})
+	if err != nil {
+		t.Errorf("NewSaltedCipher with long key, gave error %#v", err)
+	}
+}
diff --git a/blowfish/cipher.go b/blowfish/cipher.go
new file mode 100644
index 0000000..94e10f0
--- /dev/null
+++ b/blowfish/cipher.go
@@ -0,0 +1,100 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
+package blowfish
+
+// The code is a port of Bruce Schneier's C implementation.
+// See http://www.schneier.com/blowfish.html.
+
+import "strconv"
+
+// The Blowfish block size in bytes.
+const BlockSize = 8
+
+// A Cipher is an instance of Blowfish encryption using a particular key.
+type Cipher struct {
+	p              [18]uint32
+	s0, s1, s2, s3 [256]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+	return "crypto/blowfish: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a Cipher.
+// The key argument should be the Blowfish key, 4 to 56 bytes.
+func NewCipher(key []byte) (*Cipher, error) {
+	var result Cipher
+	k := len(key)
+	if k < 4 || k > 56 {
+		return nil, KeySizeError(k)
+	}
+	initCipher(key, &result)
+	ExpandKey(key, &result)
+	return &result, nil
+}
+
+// NewSaltedCipher creates a returns a Cipher that folds a salt into its key
+// schedule. For most purposes, NewCipher, instead of NewSaltedCipher, is
+// sufficient and desirable. For bcrypt compatiblity, the key can be over 56
+// bytes.
+func NewSaltedCipher(key, salt []byte) (*Cipher, error) {
+	var result Cipher
+	k := len(key)
+	if k < 4 {
+		return nil, KeySizeError(k)
+	}
+	initCipher(key, &result)
+	expandKeyWithSalt(key, salt, &result)
+	return &result, nil
+}
+
+// BlockSize returns the Blowfish block size, 8 bytes.
+// It is necessary to satisfy the Block interface in the
+// package "crypto/cipher".
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// Encrypt encrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) {
+	l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+	r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+	l, r = encryptBlock(l, r, c)
+	dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+	dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+// Decrypt decrypts the 8-byte buffer src using the key k
+// and stores the result in dst.
+func (c *Cipher) Decrypt(dst, src []byte) {
+	l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+	r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+	l, r = decryptBlock(l, r, c)
+	dst[0], dst[1], dst[2], dst[3] = byte(l>>24), byte(l>>16), byte(l>>8), byte(l)
+	dst[4], dst[5], dst[6], dst[7] = byte(r>>24), byte(r>>16), byte(r>>8), byte(r)
+}
+
+// Reset zeros the key data, so that it will no longer
+// appear in the process's memory.
+func (c *Cipher) Reset() {
+	zero(c.p[0:])
+	zero(c.s0[0:])
+	zero(c.s1[0:])
+	zero(c.s2[0:])
+	zero(c.s3[0:])
+}
+
+func initCipher(key []byte, c *Cipher) {
+	copy(c.p[0:], p[0:])
+	copy(c.s0[0:], s0[0:])
+	copy(c.s1[0:], s1[0:])
+	copy(c.s2[0:], s2[0:])
+	copy(c.s3[0:], s3[0:])
+}
diff --git a/blowfish/const.go b/blowfish/const.go
new file mode 100644
index 0000000..8c5ee4c
--- /dev/null
+++ b/blowfish/const.go
@@ -0,0 +1,199 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The startup permutation array and substitution boxes.
+// They are the hexadecimal digits of PI; see:
+// http://www.schneier.com/code/constants.txt.
+
+package blowfish
+
+var s0 = [256]uint32{
+	0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,
+	0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+	0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
+	0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+	0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,
+	0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+	0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,
+	0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+	0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
+	0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+	0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,
+	0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+	0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,
+	0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+	0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
+	0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+	0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,
+	0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+	0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,
+	0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+	0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
+	0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+	0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,
+	0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+	0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,
+	0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+	0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
+	0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+	0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,
+	0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+	0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,
+	0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+	0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
+	0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+	0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,
+	0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+	0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,
+	0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+	0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
+	0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+	0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,
+	0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+	0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
+}
+
+var s1 = [256]uint32{
+	0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,
+	0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+	0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,
+	0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+	0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,
+	0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+	0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,
+	0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+	0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,
+	0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+	0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,
+	0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+	0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,
+	0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+	0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,
+	0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+	0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,
+	0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+	0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,
+	0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+	0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,
+	0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+	0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,
+	0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+	0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,
+	0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+	0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,
+	0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+	0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,
+	0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+	0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,
+	0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+	0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,
+	0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+	0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,
+	0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+	0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,
+	0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+	0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,
+	0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+	0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,
+	0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+	0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
+}
+
+var s2 = [256]uint32{
+	0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,
+	0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+	0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,
+	0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+	0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,
+	0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+	0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,
+	0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+	0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,
+	0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+	0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,
+	0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+	0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,
+	0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+	0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,
+	0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+	0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,
+	0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+	0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,
+	0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+	0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,
+	0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+	0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,
+	0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+	0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,
+	0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+	0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,
+	0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+	0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,
+	0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+	0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,
+	0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+	0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,
+	0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+	0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,
+	0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+	0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,
+	0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+	0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,
+	0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+	0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,
+	0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+	0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
+}
+
+var s3 = [256]uint32{
+	0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
+	0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+	0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,
+	0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+	0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,
+	0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+	0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
+	0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+	0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,
+	0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+	0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,
+	0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+	0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
+	0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+	0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,
+	0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+	0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,
+	0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+	0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
+	0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+	0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,
+	0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+	0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,
+	0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+	0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
+	0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+	0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,
+	0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+	0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,
+	0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+	0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
+	0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+	0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,
+	0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+	0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,
+	0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+	0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
+	0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+	0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,
+	0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+	0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,
+	0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+	0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
+}
+
+var p = [18]uint32{
+	0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
+	0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+	0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,
+}
diff --git a/cast5/cast5.go b/cast5/cast5.go
new file mode 100644
index 0000000..889a13c
--- /dev/null
+++ b/cast5/cast5.go
@@ -0,0 +1,534 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cast5 implements CAST5, as defined in RFC 2144. CAST5 is a common
+// OpenPGP cipher.
+package cast5
+
+import "errors"
+
+const BlockSize = 8
+const KeySize = 16
+
+type Cipher struct {
+	masking [16]uint32
+	rotate  [16]uint8
+}
+
+func NewCipher(key []byte) (c *Cipher, err error) {
+	if len(key) != KeySize {
+		return nil, errors.New("CAST5: keys must be 16 bytes")
+	}
+
+	c = new(Cipher)
+	c.keySchedule(key)
+	return
+}
+
+func (c *Cipher) BlockSize() int {
+	return BlockSize
+}
+
+// Reset zeros the key material in memory.
+func (c *Cipher) Reset() {
+	for i := 0; i < 16; i++ {
+		c.masking[i] = 0
+		c.rotate[i] = 0
+	}
+}
+
+func (c *Cipher) Encrypt(dst, src []byte) {
+	l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+	r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+
+	l, r = r, l^f1(r, c.masking[0], c.rotate[0])
+	l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+	l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+	l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+
+	l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+	l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+	l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+	l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+
+	l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+	l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+	l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+	l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+
+	l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+	l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+	l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+	l, r = r, l^f1(r, c.masking[15], c.rotate[15])
+
+	dst[0] = uint8(r >> 24)
+	dst[1] = uint8(r >> 16)
+	dst[2] = uint8(r >> 8)
+	dst[3] = uint8(r)
+	dst[4] = uint8(l >> 24)
+	dst[5] = uint8(l >> 16)
+	dst[6] = uint8(l >> 8)
+	dst[7] = uint8(l)
+}
+
+func (c *Cipher) Decrypt(dst, src []byte) {
+	l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+	r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+
+	l, r = r, l^f1(r, c.masking[15], c.rotate[15])
+	l, r = r, l^f3(r, c.masking[14], c.rotate[14])
+	l, r = r, l^f2(r, c.masking[13], c.rotate[13])
+	l, r = r, l^f1(r, c.masking[12], c.rotate[12])
+
+	l, r = r, l^f3(r, c.masking[11], c.rotate[11])
+	l, r = r, l^f2(r, c.masking[10], c.rotate[10])
+	l, r = r, l^f1(r, c.masking[9], c.rotate[9])
+	l, r = r, l^f3(r, c.masking[8], c.rotate[8])
+
+	l, r = r, l^f2(r, c.masking[7], c.rotate[7])
+	l, r = r, l^f1(r, c.masking[6], c.rotate[6])
+	l, r = r, l^f3(r, c.masking[5], c.rotate[5])
+	l, r = r, l^f2(r, c.masking[4], c.rotate[4])
+
+	l, r = r, l^f1(r, c.masking[3], c.rotate[3])
+	l, r = r, l^f3(r, c.masking[2], c.rotate[2])
+	l, r = r, l^f2(r, c.masking[1], c.rotate[1])
+	l, r = r, l^f1(r, c.masking[0], c.rotate[0])
+
+	dst[0] = uint8(r >> 24)
+	dst[1] = uint8(r >> 16)
+	dst[2] = uint8(r >> 8)
+	dst[3] = uint8(r)
+	dst[4] = uint8(l >> 24)
+	dst[5] = uint8(l >> 16)
+	dst[6] = uint8(l >> 8)
+	dst[7] = uint8(l)
+}
+
+type keyScheduleA [4][7]uint8
+type keyScheduleB [4][5]uint8
+
+// keyScheduleRound contains the magic values for a round of the key schedule.
+// The keyScheduleA deals with the lines like:
+//   z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8]
+// Conceptually, both x and z are in the same array, x first. The first
+// element describes which word of this array gets written to and the
+// second, which word gets read. So, for the line above, it's "4, 0", because
+// it's writing to the first word of z, which, being after x, is word 4, and
+// reading from the first word of x: word 0.
+//
+// Next are the indexes into the S-boxes. Now the array is treated as bytes. So
+// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear
+// that it's z that we're indexing.
+//
+// keyScheduleB deals with lines like:
+//   K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2]
+// "K1" is ignored because key words are always written in order. So the five
+// elements are the S-box indexes. They use the same form as in keyScheduleA,
+// above.
+
+type keyScheduleRound struct{}
+type keySchedule []keyScheduleRound
+
+var schedule = []struct {
+	a keyScheduleA
+	b keyScheduleB
+}{
+	{
+		keyScheduleA{
+			{4, 0, 0xd, 0xf, 0xc, 0xe, 0x8},
+			{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+			{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+			{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+		},
+		keyScheduleB{
+			{16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2},
+			{16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6},
+			{16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9},
+			{16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc},
+		},
+	},
+	{
+		keyScheduleA{
+			{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+			{1, 4, 0, 2, 1, 3, 16 + 2},
+			{2, 5, 7, 6, 5, 4, 16 + 1},
+			{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+		},
+		keyScheduleB{
+			{3, 2, 0xc, 0xd, 8},
+			{1, 0, 0xe, 0xf, 0xd},
+			{7, 6, 8, 9, 3},
+			{5, 4, 0xa, 0xb, 7},
+		},
+	},
+	{
+		keyScheduleA{
+			{4, 0, 0xd, 0xf, 0xc, 0xe, 8},
+			{5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa},
+			{6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9},
+			{7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb},
+		},
+		keyScheduleB{
+			{16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9},
+			{16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc},
+			{16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2},
+			{16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6},
+		},
+	},
+	{
+		keyScheduleA{
+			{0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0},
+			{1, 4, 0, 2, 1, 3, 16 + 2},
+			{2, 5, 7, 6, 5, 4, 16 + 1},
+			{3, 7, 0xa, 9, 0xb, 8, 16 + 3},
+		},
+		keyScheduleB{
+			{8, 9, 7, 6, 3},
+			{0xa, 0xb, 5, 4, 7},
+			{0xc, 0xd, 3, 2, 8},
+			{0xe, 0xf, 1, 0, 0xd},
+		},
+	},
+}
+
+func (c *Cipher) keySchedule(in []byte) {
+	var t [8]uint32
+	var k [32]uint32
+
+	for i := 0; i < 4; i++ {
+		j := i * 4
+		t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3])
+	}
+
+	x := []byte{6, 7, 4, 5}
+	ki := 0
+
+	for half := 0; half < 2; half++ {
+		for _, round := range schedule {
+			for j := 0; j < 4; j++ {
+				var a [7]uint8
+				copy(a[:], round.a[j][:])
+				w := t[a[1]]
+				w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff]
+				w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff]
+				w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff]
+				w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff]
+				w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff]
+				t[a[0]] = w
+			}
+
+			for j := 0; j < 4; j++ {
+				var b [5]uint8
+				copy(b[:], round.b[j][:])
+				w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff]
+				w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff]
+				w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff]
+				w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff]
+				w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff]
+				k[ki] = w
+				ki++
+			}
+		}
+	}
+
+	for i := 0; i < 16; i++ {
+		c.masking[i] = k[i]
+		c.rotate[i] = uint8(k[16+i] & 0x1f)
+	}
+}
+
+// These are the three 'f' functions. See RFC 2144, section 2.2.
+func f1(d, m uint32, r uint8) uint32 {
+	t := m + d
+	I := (t << r) | (t >> (32 - r))
+	return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff]
+}
+
+func f2(d, m uint32, r uint8) uint32 {
+	t := m ^ d
+	I := (t << r) | (t >> (32 - r))
+	return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff]
+}
+
+func f3(d, m uint32, r uint8) uint32 {
+	t := m - d
+	I := (t << r) | (t >> (32 - r))
+	return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff]
+}
+
+var sBox = [8][256]uint32{
+	{
+		0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949,
+		0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e,
+		0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d,
+		0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0,
+		0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7,
+		0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935,
+		0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d,
+		0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50,
+		0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe,
+		0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3,
+		0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167,
+		0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291,
+		0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779,
+		0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2,
+		0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511,
+		0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d,
+		0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5,
+		0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324,
+		0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c,
+		0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc,
+		0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d,
+		0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96,
+		0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a,
+		0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d,
+		0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd,
+		0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6,
+		0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9,
+		0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872,
+		0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c,
+		0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e,
+		0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9,
+		0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf,
+	},
+	{
+		0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651,
+		0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3,
+		0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb,
+		0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806,
+		0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b,
+		0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359,
+		0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b,
+		0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c,
+		0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34,
+		0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb,
+		0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd,
+		0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860,
+		0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b,
+		0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304,
+		0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b,
+		0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf,
+		0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c,
+		0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13,
+		0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f,
+		0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6,
+		0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6,
+		0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58,
+		0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906,
+		0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d,
+		0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6,
+		0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4,
+		0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6,
+		0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f,
+		0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249,
+		0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa,
+		0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9,
+		0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1,
+	},
+	{
+		0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90,
+		0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5,
+		0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e,
+		0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240,
+		0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5,
+		0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b,
+		0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71,
+		0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04,
+		0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82,
+		0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15,
+		0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2,
+		0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176,
+		0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148,
+		0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc,
+		0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341,
+		0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e,
+		0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51,
+		0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f,
+		0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a,
+		0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b,
+		0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b,
+		0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5,
+		0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45,
+		0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536,
+		0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc,
+		0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0,
+		0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69,
+		0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2,
+		0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49,
+		0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d,
+		0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a,
+		0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783,
+	},
+	{
+		0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1,
+		0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf,
+		0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15,
+		0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121,
+		0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25,
+		0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5,
+		0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb,
+		0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5,
+		0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d,
+		0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6,
+		0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23,
+		0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003,
+		0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6,
+		0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119,
+		0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24,
+		0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a,
+		0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79,
+		0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df,
+		0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26,
+		0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab,
+		0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7,
+		0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417,
+		0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2,
+		0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2,
+		0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a,
+		0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919,
+		0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef,
+		0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876,
+		0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab,
+		0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04,
+		0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282,
+		0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2,
+	},
+	{
+		0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f,
+		0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a,
+		0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff,
+		0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02,
+		0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a,
+		0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7,
+		0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9,
+		0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981,
+		0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774,
+		0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655,
+		0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2,
+		0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910,
+		0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1,
+		0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da,
+		0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049,
+		0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f,
+		0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba,
+		0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be,
+		0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3,
+		0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840,
+		0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4,
+		0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2,
+		0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7,
+		0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5,
+		0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e,
+		0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e,
+		0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801,
+		0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad,
+		0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0,
+		0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20,
+		0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8,
+		0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4,
+	},
+	{
+		0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac,
+		0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138,
+		0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367,
+		0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98,
+		0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072,
+		0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3,
+		0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd,
+		0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8,
+		0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9,
+		0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54,
+		0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387,
+		0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc,
+		0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf,
+		0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf,
+		0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f,
+		0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289,
+		0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950,
+		0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f,
+		0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b,
+		0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be,
+		0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13,
+		0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976,
+		0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0,
+		0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891,
+		0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da,
+		0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc,
+		0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084,
+		0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25,
+		0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121,
+		0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5,
+		0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd,
+		0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f,
+	},
+	{
+		0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f,
+		0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de,
+		0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43,
+		0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19,
+		0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2,
+		0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516,
+		0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88,
+		0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816,
+		0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756,
+		0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a,
+		0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264,
+		0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688,
+		0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28,
+		0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3,
+		0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7,
+		0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06,
+		0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033,
+		0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a,
+		0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566,
+		0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509,
+		0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962,
+		0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e,
+		0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c,
+		0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c,
+		0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285,
+		0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301,
+		0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be,
+		0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767,
+		0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647,
+		0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914,
+		0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c,
+		0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3,
+	},
+	{
+		0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5,
+		0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc,
+		0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd,
+		0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d,
+		0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2,
+		0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862,
+		0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc,
+		0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c,
+		0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e,
+		0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039,
+		0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8,
+		0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42,
+		0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5,
+		0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472,
+		0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225,
+		0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c,
+		0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb,
+		0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054,
+		0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70,
+		0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc,
+		0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c,
+		0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3,
+		0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4,
+		0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101,
+		0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f,
+		0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e,
+		0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a,
+		0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c,
+		0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384,
+		0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c,
+		0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82,
+		0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e,
+	},
+}
diff --git a/cast5/cast5_test.go b/cast5/cast5_test.go
new file mode 100644
index 0000000..5f7025f
--- /dev/null
+++ b/cast5/cast5_test.go
@@ -0,0 +1,104 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cast5
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+)
+
+// This test vector is taken from RFC 2144, App B.1.
+// Since the other two test vectors are for reduced-round variants, we can't
+// use them.
+var basicTests = []struct {
+	key, plainText, cipherText string
+}{
+	{
+		"0123456712345678234567893456789a",
+		"0123456789abcdef",
+		"238b4fe5847e44b2",
+	},
+}
+
+func TestBasic(t *testing.T) {
+	for i, test := range basicTests {
+		key, _ := hex.DecodeString(test.key)
+		plainText, _ := hex.DecodeString(test.plainText)
+		expected, _ := hex.DecodeString(test.cipherText)
+
+		c, err := NewCipher(key)
+		if err != nil {
+			t.Errorf("#%d: failed to create Cipher: %s", i, err)
+			continue
+		}
+		var cipherText [BlockSize]byte
+		c.Encrypt(cipherText[:], plainText)
+		if !bytes.Equal(cipherText[:], expected) {
+			t.Errorf("#%d: got:%x want:%x", i, cipherText, expected)
+		}
+
+		var plainTextAgain [BlockSize]byte
+		c.Decrypt(plainTextAgain[:], cipherText[:])
+		if !bytes.Equal(plainTextAgain[:], plainText) {
+			t.Errorf("#%d: got:%x want:%x", i, plainTextAgain, plainText)
+		}
+	}
+}
+
+// TestFull performs the test specified in RFC 2144, App B.2.
+// However, due to the length of time taken, it's disabled here and a more
+// limited version is included, below.
+func TestFull(t *testing.T) {
+	// This is too slow for normal testing
+	return
+
+	a, b := iterate(1000000)
+
+	const expectedA = "eea9d0a249fd3ba6b3436fb89d6dca92"
+	const expectedB = "b2c95eb00c31ad7180ac05b8e83d696e"
+
+	if hex.EncodeToString(a) != expectedA {
+		t.Errorf("a: got:%x want:%s", a, expectedA)
+	}
+	if hex.EncodeToString(b) != expectedB {
+		t.Errorf("b: got:%x want:%s", b, expectedB)
+	}
+}
+
+func iterate(iterations int) ([]byte, []byte) {
+	const initValueHex = "0123456712345678234567893456789a"
+
+	initValue, _ := hex.DecodeString(initValueHex)
+
+	var a, b [16]byte
+	copy(a[:], initValue)
+	copy(b[:], initValue)
+
+	for i := 0; i < iterations; i++ {
+		c, _ := NewCipher(b[:])
+		c.Encrypt(a[:8], a[:8])
+		c.Encrypt(a[8:], a[8:])
+		c, _ = NewCipher(a[:])
+		c.Encrypt(b[:8], b[:8])
+		c.Encrypt(b[8:], b[8:])
+	}
+
+	return a[:], b[:]
+}
+
+func TestLimited(t *testing.T) {
+	a, b := iterate(1000)
+
+	const expectedA = "23f73b14b02a2ad7dfb9f2c35644798d"
+	const expectedB = "e5bf37eff14c456a40b21ce369370a9f"
+
+	if hex.EncodeToString(a) != expectedA {
+		t.Errorf("a: got:%x want:%s", a, expectedA)
+	}
+	if hex.EncodeToString(b) != expectedB {
+		t.Errorf("b: got:%x want:%s", b, expectedB)
+	}
+}
diff --git a/md4/md4.go b/md4/md4.go
new file mode 100644
index 0000000..c5f7c57
--- /dev/null
+++ b/md4/md4.go
@@ -0,0 +1,118 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
+package md4
+
+import (
+	"crypto"
+	"hash"
+)
+
+func init() {
+	crypto.RegisterHash(crypto.MD4, New)
+}
+
+// The size of an MD4 checksum in bytes.
+const Size = 16
+
+// The blocksize of MD4 in bytes.
+const BlockSize = 64
+
+const (
+	_Chunk = 64
+	_Init0 = 0x67452301
+	_Init1 = 0xEFCDAB89
+	_Init2 = 0x98BADCFE
+	_Init3 = 0x10325476
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+	s   [4]uint32
+	x   [_Chunk]byte
+	nx  int
+	len uint64
+}
+
+func (d *digest) Reset() {
+	d.s[0] = _Init0
+	d.s[1] = _Init1
+	d.s[2] = _Init2
+	d.s[3] = _Init3
+	d.nx = 0
+	d.len = 0
+}
+
+// New returns a new hash.Hash computing the MD4 checksum.
+func New() hash.Hash {
+	d := new(digest)
+	d.Reset()
+	return d
+}
+
+func (d *digest) Size() int { return Size }
+
+func (d *digest) BlockSize() int { return BlockSize }
+
+func (d *digest) Write(p []byte) (nn int, err error) {
+	nn = len(p)
+	d.len += uint64(nn)
+	if d.nx > 0 {
+		n := len(p)
+		if n > _Chunk-d.nx {
+			n = _Chunk - d.nx
+		}
+		for i := 0; i < n; i++ {
+			d.x[d.nx+i] = p[i]
+		}
+		d.nx += n
+		if d.nx == _Chunk {
+			_Block(d, d.x[0:])
+			d.nx = 0
+		}
+		p = p[n:]
+	}
+	n := _Block(d, p)
+	p = p[n:]
+	if len(p) > 0 {
+		d.nx = copy(d.x[:], p)
+	}
+	return
+}
+
+func (d0 *digest) Sum(in []byte) []byte {
+	// Make a copy of d0, so that caller can keep writing and summing.
+	d := new(digest)
+	*d = *d0
+
+	// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.
+	len := d.len
+	var tmp [64]byte
+	tmp[0] = 0x80
+	if len%64 < 56 {
+		d.Write(tmp[0 : 56-len%64])
+	} else {
+		d.Write(tmp[0 : 64+56-len%64])
+	}
+
+	// Length in bits.
+	len <<= 3
+	for i := uint(0); i < 8; i++ {
+		tmp[i] = byte(len >> (8 * i))
+	}
+	d.Write(tmp[0:8])
+
+	if d.nx != 0 {
+		panic("d.nx != 0")
+	}
+
+	for _, s := range d.s {
+		in = append(in, byte(s>>0))
+		in = append(in, byte(s>>8))
+		in = append(in, byte(s>>16))
+		in = append(in, byte(s>>24))
+	}
+	return in
+}
diff --git a/md4/md4_test.go b/md4/md4_test.go
new file mode 100644
index 0000000..b56edd7
--- /dev/null
+++ b/md4/md4_test.go
@@ -0,0 +1,71 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package md4
+
+import (
+	"fmt"
+	"io"
+	"testing"
+)
+
+type md4Test struct {
+	out string
+	in  string
+}
+
+var golden = []md4Test{
+	{"31d6cfe0d16ae931b73c59d7e0c089c0", ""},
+	{"bde52cb31de33e46245e05fbdbd6fb24", "a"},
+	{"ec388dd78999dfc7cf4632465693b6bf", "ab"},
+	{"a448017aaf21d8525fc10ae87aa6729d", "abc"},
+	{"41decd8f579255c5200f86a4bb3ba740", "abcd"},
+	{"9803f4a34e8eb14f96adba49064a0c41", "abcde"},
+	{"804e7f1c2586e50b49ac65db5b645131", "abcdef"},
+	{"752f4adfe53d1da0241b5bc216d098fc", "abcdefg"},
+	{"ad9daf8d49d81988590a6f0e745d15dd", "abcdefgh"},
+	{"1e4e28b05464316b56402b3815ed2dfd", "abcdefghi"},
+	{"dc959c6f5d6f9e04e4380777cc964b3d", "abcdefghij"},
+	{"1b5701e265778898ef7de5623bbe7cc0", "Discard medicine more than two years old."},
+	{"d7f087e090fe7ad4a01cb59dacc9a572", "He who has a shady past knows that nice guys finish last."},
+	{"a6f8fd6df617c72837592fc3570595c9", "I wouldn't marry him with a ten foot pole."},
+	{"c92a84a9526da8abc240c05d6b1a1ce0", "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"},
+	{"f6013160c4dcb00847069fee3bb09803", "The days of the digital watch are numbered.  -Tom Stoppard"},
+	{"2c3bb64f50b9107ed57640fe94bec09f", "Nepal premier won't resign."},
+	{"45b7d8a32c7806f2f7f897332774d6e4", "For every action there is an equal and opposite government program."},
+	{"b5b4f9026b175c62d7654bdc3a1cd438", "His money is twice tainted: 'taint yours and 'taint mine."},
+	{"caf44e80f2c20ce19b5ba1cab766e7bd", "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"},
+	{"191fae6707f496aa54a6bce9f2ecf74d", "It's a tiny change to the code and not completely disgusting. - Bob Manchek"},
+	{"9ddc753e7a4ccee6081cd1b45b23a834", "size:  a.out:  bad magic"},
+	{"8d050f55b1cadb9323474564be08a521", "The major problem is with sendmail.  -Mark Horton"},
+	{"ad6e2587f74c3e3cc19146f6127fa2e3", "Give me a rock, paper and scissors and I will move the world.  CCFestoon"},
+	{"1d616d60a5fabe85589c3f1566ca7fca", "If the enemy is within range, then so are you."},
+	{"aec3326a4f496a2ced65a1963f84577f", "It's well we cannot hear the screams/That we create in others' dreams."},
+	{"77b4fd762d6b9245e61c50bf6ebf118b", "You remind me of a TV show, but that's all right: I watch it anyway."},
+	{"e8f48c726bae5e516f6ddb1a4fe62438", "C is as portable as Stonehedge!!"},
+	{"a3a84366e7219e887423b01f9be7166e", "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"},
+	{"a6b7aa35157e984ef5d9b7f32e5fbb52", "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction.  Lewis-Randall Rule"},
+	{"75661f0545955f8f9abeeb17845f3fd6", "How can you write a big system without C++?  -Paul Glick"},
+}
+
+func TestGolden(t *testing.T) {
+	for i := 0; i < len(golden); i++ {
+		g := golden[i]
+		c := New()
+		for j := 0; j < 3; j++ {
+			if j < 2 {
+				io.WriteString(c, g.in)
+			} else {
+				io.WriteString(c, g.in[0:len(g.in)/2])
+				c.Sum(nil)
+				io.WriteString(c, g.in[len(g.in)/2:])
+			}
+			s := fmt.Sprintf("%x", c.Sum(nil))
+			if s != g.out {
+				t.Fatalf("md4[%d](%s) = %s want %s", j, g.in, s, g.out)
+			}
+			c.Reset()
+		}
+	}
+}
diff --git a/md4/md4block.go b/md4/md4block.go
new file mode 100644
index 0000000..3fed475
--- /dev/null
+++ b/md4/md4block.go
@@ -0,0 +1,89 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// MD4 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package md4
+
+var shift1 = []uint{3, 7, 11, 19}
+var shift2 = []uint{3, 5, 9, 13}
+var shift3 = []uint{3, 9, 11, 15}
+
+var xIndex2 = []uint{0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15}
+var xIndex3 = []uint{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
+
+func _Block(dig *digest, p []byte) int {
+	a := dig.s[0]
+	b := dig.s[1]
+	c := dig.s[2]
+	d := dig.s[3]
+	n := 0
+	var X [16]uint32
+	for len(p) >= _Chunk {
+		aa, bb, cc, dd := a, b, c, d
+
+		j := 0
+		for i := 0; i < 16; i++ {
+			X[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
+			j += 4
+		}
+
+		// If this needs to be made faster in the future,
+		// the usual trick is to unroll each of these
+		// loops by a factor of 4; that lets you replace
+		// the shift[] lookups with constants and,
+		// with suitable variable renaming in each
+		// unrolled body, delete the a, b, c, d = d, a, b, c
+		// (or you can let the optimizer do the renaming).
+		//
+		// The index variables are uint so that % by a power
+		// of two can be optimized easily by a compiler.
+
+		// Round 1.
+		for i := uint(0); i < 16; i++ {
+			x := i
+			s := shift1[i%4]
+			f := ((c ^ d) & b) ^ d
+			a += f + X[x]
+			a = a<<s | a>>(32-s)
+			a, b, c, d = d, a, b, c
+		}
+
+		// Round 2.
+		for i := uint(0); i < 16; i++ {
+			x := xIndex2[i]
+			s := shift2[i%4]
+			g := (b & c) | (b & d) | (c & d)
+			a += g + X[x] + 0x5a827999
+			a = a<<s | a>>(32-s)
+			a, b, c, d = d, a, b, c
+		}
+
+		// Round 3.
+		for i := uint(0); i < 16; i++ {
+			x := xIndex3[i]
+			s := shift3[i%4]
+			h := b ^ c ^ d
+			a += h + X[x] + 0x6ed9eba1
+			a = a<<s | a>>(32-s)
+			a, b, c, d = d, a, b, c
+		}
+
+		a += aa
+		b += bb
+		c += cc
+		d += dd
+
+		p = p[_Chunk:]
+		n += _Chunk
+	}
+
+	dig.s[0] = a
+	dig.s[1] = b
+	dig.s[2] = c
+	dig.s[3] = d
+	return n
+}
diff --git a/ocsp/ocsp.go b/ocsp/ocsp.go
new file mode 100644
index 0000000..b9dfdf9
--- /dev/null
+++ b/ocsp/ocsp.go
@@ -0,0 +1,191 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses
+// are signed messages attesting to the validity of a certificate for a small
+// period of time. This is used to manage revocation for X.509 certificates.
+package ocsp
+
+import (
+	"crypto"
+	"crypto/rsa"
+	_ "crypto/sha1"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/asn1"
+	"time"
+)
+
+var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1})
+var idSHA1WithRSA = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 1, 5})
+
+// These are internal structures that reflect the ASN.1 structure of an OCSP
+// response. See RFC 2560, section 4.2.
+
+const (
+	ocspSuccess       = 0
+	ocspMalformed     = 1
+	ocspInternalError = 2
+	ocspTryLater      = 3
+	ocspSigRequired   = 4
+	ocspUnauthorized  = 5
+)
+
+type certID struct {
+	HashAlgorithm pkix.AlgorithmIdentifier
+	NameHash      []byte
+	IssuerKeyHash []byte
+	SerialNumber  asn1.RawValue
+}
+
+type responseASN1 struct {
+	Status   asn1.Enumerated
+	Response responseBytes `asn1:"explicit,tag:0"`
+}
+
+type responseBytes struct {
+	ResponseType asn1.ObjectIdentifier
+	Response     []byte
+}
+
+type basicResponse struct {
+	TBSResponseData    responseData
+	SignatureAlgorithm pkix.AlgorithmIdentifier
+	Signature          asn1.BitString
+	Certificates       []asn1.RawValue `asn1:"explicit,tag:0,optional"`
+}
+
+type responseData struct {
+	Raw           asn1.RawContent
+	Version       int              `asn1:"optional,default:1,explicit,tag:0"`
+	RequestorName pkix.RDNSequence `asn1:"optional,explicit,tag:1"`
+	KeyHash       []byte           `asn1:"optional,explicit,tag:2"`
+	ProducedAt    time.Time
+	Responses     []singleResponse
+}
+
+type singleResponse struct {
+	CertID     certID
+	Good       asn1.Flag   `asn1:"explicit,tag:0,optional"`
+	Revoked    revokedInfo `asn1:"explicit,tag:1,optional"`
+	Unknown    asn1.Flag   `asn1:"explicit,tag:2,optional"`
+	ThisUpdate time.Time
+	NextUpdate time.Time `asn1:"explicit,tag:0,optional"`
+}
+
+type revokedInfo struct {
+	RevocationTime time.Time
+	Reason         int `asn1:"explicit,tag:0,optional"`
+}
+
+// This is the exposed reflection of the internal OCSP structures.
+
+const (
+	// Good means that the certificate is valid.
+	Good = iota
+	// Revoked means that the certificate has been deliberately revoked.
+	Revoked = iota
+	// Unknown means that the OCSP responder doesn't know about the certificate.
+	Unknown = iota
+	// ServerFailed means that the OCSP responder failed to process the request.
+	ServerFailed = iota
+)
+
+// Response represents an OCSP response. See RFC 2560.
+type Response struct {
+	// Status is one of {Good, Revoked, Unknown, ServerFailed}
+	Status                                        int
+	SerialNumber                                  []byte
+	ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time
+	RevocationReason                              int
+	Certificate                                   *x509.Certificate
+}
+
+// ParseError results from an invalid OCSP response.
+type ParseError string
+
+func (p ParseError) Error() string {
+	return string(p)
+}
+
+// ParseResponse parses an OCSP response in DER form. It only supports
+// responses for a single certificate and only those using RSA signatures.
+// Non-RSA responses will result in an x509.UnsupportedAlgorithmError.
+// Signature errors or parse failures will result in a ParseError.
+func ParseResponse(bytes []byte) (*Response, error) {
+	var resp responseASN1
+	rest, err := asn1.Unmarshal(bytes, &resp)
+	if err != nil {
+		return nil, err
+	}
+	if len(rest) > 0 {
+		return nil, ParseError("trailing data in OCSP response")
+	}
+
+	ret := new(Response)
+	if resp.Status != ocspSuccess {
+		ret.Status = ServerFailed
+		return ret, nil
+	}
+
+	if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) {
+		return nil, ParseError("bad OCSP response type")
+	}
+
+	var basicResp basicResponse
+	rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(basicResp.Certificates) != 1 {
+		return nil, ParseError("OCSP response contains bad number of certificates")
+	}
+
+	if len(basicResp.TBSResponseData.Responses) != 1 {
+		return nil, ParseError("OCSP response contains bad number of responses")
+	}
+
+	ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	if ret.Certificate.PublicKeyAlgorithm != x509.RSA || !basicResp.SignatureAlgorithm.Algorithm.Equal(idSHA1WithRSA) {
+		return nil, x509.UnsupportedAlgorithmError{}
+	}
+
+	hashType := crypto.SHA1
+	h := hashType.New()
+
+	pub := ret.Certificate.PublicKey.(*rsa.PublicKey)
+	h.Write(basicResp.TBSResponseData.Raw)
+	digest := h.Sum(nil)
+	signature := basicResp.Signature.RightAlign()
+
+	if rsa.VerifyPKCS1v15(pub, hashType, digest, signature) != nil {
+		return nil, ParseError("bad OCSP signature")
+	}
+
+	r := basicResp.TBSResponseData.Responses[0]
+
+	ret.SerialNumber = r.CertID.SerialNumber.Bytes
+
+	switch {
+	case bool(r.Good):
+		ret.Status = Good
+	case bool(r.Unknown):
+		ret.Status = Unknown
+	default:
+		ret.Status = Revoked
+		ret.RevokedAt = r.Revoked.RevocationTime
+		ret.RevocationReason = r.Revoked.Reason
+	}
+
+	ret.ProducedAt = basicResp.TBSResponseData.ProducedAt
+	ret.ThisUpdate = r.ThisUpdate
+	ret.NextUpdate = r.NextUpdate
+
+	return ret, nil
+}
diff --git a/ocsp/ocsp_test.go b/ocsp/ocsp_test.go
new file mode 100644
index 0000000..f0e9f94
--- /dev/null
+++ b/ocsp/ocsp_test.go
@@ -0,0 +1,107 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ocsp
+
+import (
+	"bytes"
+	"encoding/hex"
+	"reflect"
+	"testing"
+	"time"
+)
+
+func TestOCSPDecode(t *testing.T) {
+	responseBytes, _ := hex.DecodeString(ocspResponseHex)
+	resp, err := ParseResponse(responseBytes)
+	if err != nil {
+		t.Error(err)
+	}
+
+	expected := Response{
+		Status:           0,
+		SerialNumber:     []byte{0x1, 0xd0, 0xfa},
+		RevocationReason: 0,
+		ThisUpdate:       time.Date(2010, 7, 7, 15, 1, 5, 0, time.UTC),
+		NextUpdate:       time.Date(2010, 7, 7, 18, 35, 17, 0, time.UTC),
+	}
+
+	if !reflect.DeepEqual(resp.ThisUpdate, expected.ThisUpdate) {
+		t.Errorf("resp.ThisUpdate: got %d, want %d", resp.ThisUpdate, expected.ThisUpdate)
+	}
+
+	if !reflect.DeepEqual(resp.NextUpdate, expected.NextUpdate) {
+		t.Errorf("resp.NextUpdate: got %d, want %d", resp.NextUpdate, expected.NextUpdate)
+	}
+
+	if resp.Status != expected.Status {
+		t.Errorf("resp.Status: got %d, want %d", resp.Status, expected.Status)
+	}
+
+	if !bytes.Equal(resp.SerialNumber, expected.SerialNumber) {
+		t.Errorf("resp.SerialNumber: got %x, want %x", resp.SerialNumber, expected.SerialNumber)
+	}
+
+	if resp.RevocationReason != expected.RevocationReason {
+		t.Errorf("resp.RevocationReason: got %d, want %d", resp.RevocationReason, expected.RevocationReason)
+	}
+}
+
+// This OCSP response was taken from Thawte's public OCSP responder.
+// To recreate:
+//   $ openssl s_client -tls1 -showcerts -servername www.google.com -connect www.google.com:443
+// Copy and paste the first certificate into /tmp/cert.crt and the second into
+// /tmp/intermediate.crt
+//   $ openssl ocsp -issuer /tmp/intermediate.crt -cert /tmp/cert.crt -url http://ocsp.thawte.com -resp_text -respout /tmp/ocsp.der
+// Then hex encode the result:
+//   $ python -c 'print file("/tmp/ocsp.der", "r").read().encode("hex")'
+
+const ocspResponseHex = "308206bc0a0100a08206b5308206b106092b0601050507300101048206a23082069e3081" +
+	"c9a14e304c310b300906035504061302494c31163014060355040a130d5374617274436f" +
+	"6d204c74642e312530230603550403131c5374617274436f6d20436c6173732031204f43" +
+	"5350205369676e6572180f32303130303730373137333531375a30663064303c30090605" +
+	"2b0e03021a050004146568874f40750f016a3475625e1f5c93e5a26d580414eb4234d098" +
+	"b0ab9ff41b6b08f7cc642eef0e2c45020301d0fa8000180f323031303037303731353031" +
+	"30355aa011180f32303130303730373138333531375a300d06092a864886f70d01010505" +
+	"000382010100ab557ff070d1d7cebbb5f0ec91a15c3fed22eb2e1b8244f1b84545f013a4" +
+	"fb46214c5e3fbfbebb8a56acc2b9db19f68fd3c3201046b3824d5ba689f99864328710cb" +
+	"467195eb37d84f539e49f859316b32964dc3e47e36814ce94d6c56dd02733b1d0802f7ff" +
+	"4eebdbbd2927dcf580f16cbc290f91e81b53cb365e7223f1d6e20a88ea064104875e0145" +
+	"672b20fc14829d51ca122f5f5d77d3ad6c83889c55c7dc43680ba2fe3cef8b05dbcabdc0" +
+	"d3e09aaf9725597f8c858c2fa38c0d6aed2e6318194420dd1a1137445d13e1c97ab47896" +
+	"17a4e08925f46f867b72e3a4dc1f08cb870b2b0717f7207faa0ac512e628a029aba7457a" +
+	"e63dcf3281e2162d9349a08204ba308204b6308204b23082039aa003020102020101300d" +
+	"06092a864886f70d010105050030818c310b300906035504061302494c31163014060355" +
+	"040a130d5374617274436f6d204c74642e312b3029060355040b13225365637572652044" +
+	"69676974616c204365727469666963617465205369676e696e6731383036060355040313" +
+	"2f5374617274436f6d20436c6173732031205072696d61727920496e7465726d65646961" +
+	"746520536572766572204341301e170d3037313032353030323330365a170d3132313032" +
+	"333030323330365a304c310b300906035504061302494c31163014060355040a130d5374" +
+	"617274436f6d204c74642e312530230603550403131c5374617274436f6d20436c617373" +
+	"2031204f435350205369676e657230820122300d06092a864886f70d0101010500038201" +
+	"0f003082010a0282010100b9561b4c45318717178084e96e178df2255e18ed8d8ecc7c2b" +
+	"7b51a6c1c2e6bf0aa3603066f132fe10ae97b50e99fa24b83fc53dd2777496387d14e1c3" +
+	"a9b6a4933e2ac12413d085570a95b8147414a0bc007c7bcf222446ef7f1a156d7ea1c577" +
+	"fc5f0facdfd42eb0f5974990cb2f5cefebceef4d1bdc7ae5c1075c5a99a93171f2b0845b" +
+	"4ff0864e973fcfe32f9d7511ff87a3e943410c90a4493a306b6944359340a9ca96f02b66" +
+	"ce67f028df2980a6aaee8d5d5d452b8b0eb93f923cc1e23fcccbdbe7ffcb114d08fa7a6a" +
+	"3c404f825d1a0e715935cf623a8c7b59670014ed0622f6089a9447a7a19010f7fe58f841" +
+	"29a2765ea367824d1c3bb2fda308530203010001a382015c30820158300c0603551d1301" +
+	"01ff04023000300b0603551d0f0404030203a8301e0603551d250417301506082b060105" +
+	"0507030906092b0601050507300105301d0603551d0e0416041445e0a36695414c5dd449" +
+	"bc00e33cdcdbd2343e173081a80603551d230481a030819d8014eb4234d098b0ab9ff41b" +
+	"6b08f7cc642eef0e2c45a18181a47f307d310b300906035504061302494c311630140603" +
+	"55040a130d5374617274436f6d204c74642e312b3029060355040b132253656375726520" +
+	"4469676974616c204365727469666963617465205369676e696e67312930270603550403" +
+	"13205374617274436f6d2043657274696669636174696f6e20417574686f726974798201" +
+	"0a30230603551d12041c301a8618687474703a2f2f7777772e737461727473736c2e636f" +
+	"6d2f302c06096086480186f842010d041f161d5374617274436f6d205265766f63617469" +
+	"6f6e20417574686f72697479300d06092a864886f70d01010505000382010100182d2215" +
+	"8f0fc0291324fa8574c49bb8ff2835085adcbf7b7fc4191c397ab6951328253fffe1e5ec" +
+	"2a7da0d50fca1a404e6968481366939e666c0a6209073eca57973e2fefa9ed1718e8176f" +
+	"1d85527ff522c08db702e3b2b180f1cbff05d98128252cf0f450f7dd2772f4188047f19d" +
+	"c85317366f94bc52d60f453a550af58e308aaab00ced33040b62bf37f5b1ab2a4f7f0f80" +
+	"f763bf4d707bc8841d7ad9385ee2a4244469260b6f2bf085977af9074796048ecc2f9d48" +
+	"a1d24ce16e41a9941568fec5b42771e118f16c106a54ccc339a4b02166445a167902e75e" +
+	"6d8620b0825dcd18a069b90fd851d10fa8effd409deec02860d26d8d833f304b10669b42"
diff --git a/openpgp/armor/armor.go b/openpgp/armor/armor.go
new file mode 100644
index 0000000..8bfab05
--- /dev/null
+++ b/openpgp/armor/armor.go
@@ -0,0 +1,219 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is
+// very similar to PEM except that it has an additional CRC checksum.
+package armor
+
+import (
+	"bufio"
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"encoding/base64"
+	"io"
+)
+
+// A Block represents an OpenPGP armored structure.
+//
+// The encoded form is:
+//    -----BEGIN Type-----
+//    Headers
+//
+//    base64-encoded Bytes
+//    '=' base64 encoded checksum
+//    -----END Type-----
+// where Headers is a possibly empty sequence of Key: Value lines.
+//
+// Since the armored data can be very large, this package presents a streaming
+// interface.
+type Block struct {
+	Type    string            // The type, taken from the preamble (i.e. "PGP SIGNATURE").
+	Header  map[string]string // Optional headers.
+	Body    io.Reader         // A Reader from which the contents can be read
+	lReader lineReader
+	oReader openpgpReader
+}
+
+var ArmorCorrupt error = errors.StructuralError("armor invalid")
+
+const crc24Init = 0xb704ce
+const crc24Poly = 0x1864cfb
+const crc24Mask = 0xffffff
+
+// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1
+func crc24(crc uint32, d []byte) uint32 {
+	for _, b := range d {
+		crc ^= uint32(b) << 16
+		for i := 0; i < 8; i++ {
+			crc <<= 1
+			if crc&0x1000000 != 0 {
+				crc ^= crc24Poly
+			}
+		}
+	}
+	return crc
+}
+
+var armorStart = []byte("-----BEGIN ")
+var armorEnd = []byte("-----END ")
+var armorEndOfLine = []byte("-----")
+
+// lineReader wraps a line based reader. It watches for the end of an armor
+// block and records the expected CRC value.
+type lineReader struct {
+	in  *bufio.Reader
+	buf []byte
+	eof bool
+	crc uint32
+}
+
+func (l *lineReader) Read(p []byte) (n int, err error) {
+	if l.eof {
+		return 0, io.EOF
+	}
+
+	if len(l.buf) > 0 {
+		n = copy(p, l.buf)
+		l.buf = l.buf[n:]
+		return
+	}
+
+	line, isPrefix, err := l.in.ReadLine()
+	if err != nil {
+		return
+	}
+	if isPrefix {
+		return 0, ArmorCorrupt
+	}
+
+	if len(line) == 5 && line[0] == '=' {
+		// This is the checksum line
+		var expectedBytes [3]byte
+		var m int
+		m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:])
+		if m != 3 || err != nil {
+			return
+		}
+		l.crc = uint32(expectedBytes[0])<<16 |
+			uint32(expectedBytes[1])<<8 |
+			uint32(expectedBytes[2])
+
+		line, _, err = l.in.ReadLine()
+		if err != nil && err != io.EOF {
+			return
+		}
+		if !bytes.HasPrefix(line, armorEnd) {
+			return 0, ArmorCorrupt
+		}
+
+		l.eof = true
+		return 0, io.EOF
+	}
+
+	if len(line) > 64 {
+		return 0, ArmorCorrupt
+	}
+
+	n = copy(p, line)
+	bytesToSave := len(line) - n
+	if bytesToSave > 0 {
+		if cap(l.buf) < bytesToSave {
+			l.buf = make([]byte, 0, bytesToSave)
+		}
+		l.buf = l.buf[0:bytesToSave]
+		copy(l.buf, line[n:])
+	}
+
+	return
+}
+
+// openpgpReader passes Read calls to the underlying base64 decoder, but keeps
+// a running CRC of the resulting data and checks the CRC against the value
+// found by the lineReader at EOF.
+type openpgpReader struct {
+	lReader    *lineReader
+	b64Reader  io.Reader
+	currentCRC uint32
+}
+
+func (r *openpgpReader) Read(p []byte) (n int, err error) {
+	n, err = r.b64Reader.Read(p)
+	r.currentCRC = crc24(r.currentCRC, p[:n])
+
+	if err == io.EOF {
+		if r.lReader.crc != uint32(r.currentCRC&crc24Mask) {
+			return 0, ArmorCorrupt
+		}
+	}
+
+	return
+}
+
+// Decode reads a PGP armored block from the given Reader. It will ignore
+// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The
+// given Reader is not usable after calling this function: an arbitrary amount
+// of data may have been read past the end of the block.
+func Decode(in io.Reader) (p *Block, err error) {
+	r, _ := bufio.NewReaderSize(in, 100)
+	var line []byte
+	ignoreNext := false
+
+TryNextBlock:
+	p = nil
+
+	// Skip leading garbage
+	for {
+		ignoreThis := ignoreNext
+		line, ignoreNext, err = r.ReadLine()
+		if err != nil {
+			return
+		}
+		if ignoreNext || ignoreThis {
+			continue
+		}
+		line = bytes.TrimSpace(line)
+		if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) {
+			break
+		}
+	}
+
+	p = new(Block)
+	p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)])
+	p.Header = make(map[string]string)
+	nextIsContinuation := false
+	var lastKey string
+
+	// Read headers
+	for {
+		isContinuation := nextIsContinuation
+		line, nextIsContinuation, err = r.ReadLine()
+		if err != nil {
+			p = nil
+			return
+		}
+		if isContinuation {
+			p.Header[lastKey] += string(line)
+			continue
+		}
+		line = bytes.TrimSpace(line)
+		if len(line) == 0 {
+			break
+		}
+
+		i := bytes.Index(line, []byte(": "))
+		if i == -1 {
+			goto TryNextBlock
+		}
+		lastKey = string(line[:i])
+		p.Header[lastKey] = string(line[i+2:])
+	}
+
+	p.lReader.in = r
+	p.oReader.currentCRC = crc24Init
+	p.oReader.lReader = &p.lReader
+	p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader)
+	p.Body = &p.oReader
+
+	return
+}
diff --git a/openpgp/armor/armor_test.go b/openpgp/armor/armor_test.go
new file mode 100644
index 0000000..9334e94
--- /dev/null
+++ b/openpgp/armor/armor_test.go
@@ -0,0 +1,95 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package armor
+
+import (
+	"bytes"
+	"hash/adler32"
+	"io/ioutil"
+	"testing"
+)
+
+func TestDecodeEncode(t *testing.T) {
+	buf := bytes.NewBuffer([]byte(armorExample1))
+	result, err := Decode(buf)
+	if err != nil {
+		t.Error(err)
+	}
+	expectedType := "PGP SIGNATURE"
+	if result.Type != expectedType {
+		t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType)
+	}
+	if len(result.Header) != 1 {
+		t.Errorf("len(result.Header): got:%d want:1", len(result.Header))
+	}
+	v, ok := result.Header["Version"]
+	if !ok || v != "GnuPG v1.4.10 (GNU/Linux)" {
+		t.Errorf("result.Header: got:%#v", result.Header)
+	}
+
+	contents, err := ioutil.ReadAll(result.Body)
+	if err != nil {
+		t.Error(err)
+	}
+
+	if adler32.Checksum(contents) != 0x27b144be {
+		t.Errorf("contents: got: %x", contents)
+	}
+
+	buf = bytes.NewBuffer(nil)
+	w, err := Encode(buf, result.Type, result.Header)
+	if err != nil {
+		t.Error(err)
+	}
+	_, err = w.Write(contents)
+	if err != nil {
+		t.Error(err)
+	}
+	w.Close()
+
+	if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) {
+		t.Errorf("got: %s\nwant: %s", string(buf.Bytes()), armorExample1)
+	}
+}
+
+func TestLongHeader(t *testing.T) {
+	buf := bytes.NewBuffer([]byte(armorLongLine))
+	result, err := Decode(buf)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	value, ok := result.Header["Version"]
+	if !ok {
+		t.Errorf("missing Version header")
+	}
+	if value != longValueExpected {
+		t.Errorf("got: %s want: %s", value, longValueExpected)
+	}
+}
+
+const armorExample1 = `-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm
+4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt
+p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW
+TxRjs+fJCIFuo71xb1g=
+=/teI
+-----END PGP SIGNATURE-----`
+
+const armorLongLine = `-----BEGIN PGP SIGNATURE-----
+Version: 0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz
+
+iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8
+kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp
+cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA
+byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3
+WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv
+okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4=
+=wfQG
+-----END PGP SIGNATURE-----`
+
+const longValueExpected = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz"
diff --git a/openpgp/armor/encode.go b/openpgp/armor/encode.go
new file mode 100644
index 0000000..6f07582
--- /dev/null
+++ b/openpgp/armor/encode.go
@@ -0,0 +1,160 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package armor
+
+import (
+	"encoding/base64"
+	"io"
+)
+
+var armorHeaderSep = []byte(": ")
+var blockEnd = []byte("\n=")
+var newline = []byte("\n")
+var armorEndOfLineOut = []byte("-----\n")
+
+// writeSlices writes its arguments to the given Writer.
+func writeSlices(out io.Writer, slices ...[]byte) (err error) {
+	for _, s := range slices {
+		_, err = out.Write(s)
+		if err != nil {
+			return err
+		}
+	}
+	return
+}
+
+// lineBreaker breaks data across several lines, all of the same byte length
+// (except possibly the last). Lines are broken with a single '\n'.
+type lineBreaker struct {
+	lineLength  int
+	line        []byte
+	used        int
+	out         io.Writer
+	haveWritten bool
+}
+
+func newLineBreaker(out io.Writer, lineLength int) *lineBreaker {
+	return &lineBreaker{
+		lineLength: lineLength,
+		line:       make([]byte, lineLength),
+		used:       0,
+		out:        out,
+	}
+}
+
+func (l *lineBreaker) Write(b []byte) (n int, err error) {
+	n = len(b)
+
+	if n == 0 {
+		return
+	}
+
+	if l.used == 0 && l.haveWritten {
+		_, err = l.out.Write([]byte{'\n'})
+		if err != nil {
+			return
+		}
+	}
+
+	if l.used+len(b) < l.lineLength {
+		l.used += copy(l.line[l.used:], b)
+		return
+	}
+
+	l.haveWritten = true
+	_, err = l.out.Write(l.line[0:l.used])
+	if err != nil {
+		return
+	}
+	excess := l.lineLength - l.used
+	l.used = 0
+
+	_, err = l.out.Write(b[0:excess])
+	if err != nil {
+		return
+	}
+
+	_, err = l.Write(b[excess:])
+	return
+}
+
+func (l *lineBreaker) Close() (err error) {
+	if l.used > 0 {
+		_, err = l.out.Write(l.line[0:l.used])
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// encoding keeps track of a running CRC24 over the data which has been written
+// to it and outputs a OpenPGP checksum when closed, followed by an armor
+// trailer.
+//
+// It's built into a stack of io.Writers:
+//    encoding -> base64 encoder -> lineBreaker -> out
+type encoding struct {
+	out       io.Writer
+	breaker   *lineBreaker
+	b64       io.WriteCloser
+	crc       uint32
+	blockType []byte
+}
+
+func (e *encoding) Write(data []byte) (n int, err error) {
+	e.crc = crc24(e.crc, data)
+	return e.b64.Write(data)
+}
+
+func (e *encoding) Close() (err error) {
+	err = e.b64.Close()
+	if err != nil {
+		return
+	}
+	e.breaker.Close()
+
+	var checksumBytes [3]byte
+	checksumBytes[0] = byte(e.crc >> 16)
+	checksumBytes[1] = byte(e.crc >> 8)
+	checksumBytes[2] = byte(e.crc)
+
+	var b64ChecksumBytes [4]byte
+	base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:])
+
+	return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine)
+}
+
+// Encode returns a WriteCloser which will encode the data written to it in
+// OpenPGP armor.
+func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) {
+	bType := []byte(blockType)
+	err = writeSlices(out, armorStart, bType, armorEndOfLineOut)
+	if err != nil {
+		return
+	}
+
+	for k, v := range headers {
+		err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline)
+		if err != nil {
+			return
+		}
+	}
+
+	_, err = out.Write(newline)
+	if err != nil {
+		return
+	}
+
+	e := &encoding{
+		out:       out,
+		breaker:   newLineBreaker(out, 64),
+		crc:       crc24Init,
+		blockType: bType,
+	}
+	e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker)
+	return e, nil
+}
diff --git a/openpgp/canonical_text.go b/openpgp/canonical_text.go
new file mode 100644
index 0000000..e601e38
--- /dev/null
+++ b/openpgp/canonical_text.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import "hash"
+
+// NewCanonicalTextHash reformats text written to it into the canonical
+// form and then applies the hash h.  See RFC 4880, section 5.2.1.
+func NewCanonicalTextHash(h hash.Hash) hash.Hash {
+	return &canonicalTextHash{h, 0}
+}
+
+type canonicalTextHash struct {
+	h hash.Hash
+	s int
+}
+
+var newline = []byte{'\r', '\n'}
+
+func (cth *canonicalTextHash) Write(buf []byte) (int, error) {
+	start := 0
+
+	for i, c := range buf {
+		switch cth.s {
+		case 0:
+			if c == '\r' {
+				cth.s = 1
+			} else if c == '\n' {
+				cth.h.Write(buf[start:i])
+				cth.h.Write(newline)
+				start = i + 1
+			}
+		case 1:
+			cth.s = 0
+		}
+	}
+
+	cth.h.Write(buf[start:])
+	return len(buf), nil
+}
+
+func (cth *canonicalTextHash) Sum(in []byte) []byte {
+	return cth.h.Sum(in)
+}
+
+func (cth *canonicalTextHash) Reset() {
+	cth.h.Reset()
+	cth.s = 0
+}
+
+func (cth *canonicalTextHash) Size() int {
+	return cth.h.Size()
+}
+
+func (cth *canonicalTextHash) BlockSize() int {
+	return cth.h.BlockSize()
+}
diff --git a/openpgp/canonical_text_test.go b/openpgp/canonical_text_test.go
new file mode 100644
index 0000000..8f3ba2a
--- /dev/null
+++ b/openpgp/canonical_text_test.go
@@ -0,0 +1,52 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"testing"
+)
+
+type recordingHash struct {
+	buf *bytes.Buffer
+}
+
+func (r recordingHash) Write(b []byte) (n int, err error) {
+	return r.buf.Write(b)
+}
+
+func (r recordingHash) Sum(in []byte) []byte {
+	return append(in, r.buf.Bytes()...)
+}
+
+func (r recordingHash) Reset() {
+	panic("shouldn't be called")
+}
+
+func (r recordingHash) Size() int {
+	panic("shouldn't be called")
+}
+
+func (r recordingHash) BlockSize() int {
+	panic("shouldn't be called")
+}
+
+func testCanonicalText(t *testing.T, input, expected string) {
+	r := recordingHash{bytes.NewBuffer(nil)}
+	c := NewCanonicalTextHash(r)
+	c.Write([]byte(input))
+	result := c.Sum(nil)
+	if expected != string(result) {
+		t.Errorf("input: %x got: %x want: %x", input, result, expected)
+	}
+}
+
+func TestCanonicalText(t *testing.T) {
+	testCanonicalText(t, "foo\n", "foo\r\n")
+	testCanonicalText(t, "foo", "foo")
+	testCanonicalText(t, "foo\r\n", "foo\r\n")
+	testCanonicalText(t, "foo\r\nbar", "foo\r\nbar")
+	testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n")
+}
diff --git a/openpgp/elgamal/elgamal.go b/openpgp/elgamal/elgamal.go
new file mode 100644
index 0000000..a553bde
--- /dev/null
+++ b/openpgp/elgamal/elgamal.go
@@ -0,0 +1,122 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package elgamal implements ElGamal encryption, suitable for OpenPGP,
+// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on
+// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31,
+// n. 4, 1985, pp. 469-472.
+//
+// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it
+// unsuitable for other protocols. RSA should be used in preference in any
+// case.
+package elgamal
+
+import (
+	"crypto/rand"
+	"crypto/subtle"
+	"errors"
+	"io"
+	"math/big"
+)
+
+// PublicKey represents an ElGamal public key.
+type PublicKey struct {
+	G, P, Y *big.Int
+}
+
+// PrivateKey represents an ElGamal private key.
+type PrivateKey struct {
+	PublicKey
+	X *big.Int
+}
+
+// Encrypt encrypts the given message to the given public key. The result is a
+// pair of integers. Errors can result from reading random, or because msg is
+// too large to be encrypted to the public key.
+func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) {
+	pLen := (pub.P.BitLen() + 7) / 8
+	if len(msg) > pLen-11 {
+		err = errors.New("elgamal: message too long")
+		return
+	}
+
+	// EM = 0x02 || PS || 0x00 || M
+	em := make([]byte, pLen-1)
+	em[0] = 2
+	ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):]
+	err = nonZeroRandomBytes(ps, random)
+	if err != nil {
+		return
+	}
+	em[len(em)-len(msg)-1] = 0
+	copy(mm, msg)
+
+	m := new(big.Int).SetBytes(em)
+
+	k, err := rand.Int(random, pub.P)
+	if err != nil {
+		return
+	}
+
+	c1 = new(big.Int).Exp(pub.G, k, pub.P)
+	s := new(big.Int).Exp(pub.Y, k, pub.P)
+	c2 = s.Mul(s, m)
+	c2.Mod(c2, pub.P)
+
+	return
+}
+
+// Decrypt takes two integers, resulting from an ElGamal encryption, and
+// returns the plaintext of the message. An error can result only if the
+// ciphertext is invalid. Users should keep in mind that this is a padding
+// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
+// be used to break the cryptosystem.  See ``Chosen Ciphertext Attacks
+// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel
+// Bleichenbacher, Advances in Cryptology (Crypto '98),
+func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
+	s := new(big.Int).Exp(c1, priv.X, priv.P)
+	s.ModInverse(s, priv.P)
+	s.Mul(s, c2)
+	s.Mod(s, priv.P)
+	em := s.Bytes()
+
+	firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2)
+
+	// The remainder of the plaintext must be a string of non-zero random
+	// octets, followed by a 0, followed by the message.
+	//   lookingForIndex: 1 iff we are still looking for the zero.
+	//   index: the offset of the first zero byte.
+	var lookingForIndex, index int
+	lookingForIndex = 1
+
+	for i := 1; i < len(em); i++ {
+		equals0 := subtle.ConstantTimeByteEq(em[i], 0)
+		index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index)
+		lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex)
+	}
+
+	if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 {
+		return nil, errors.New("elgamal: decryption error")
+	}
+	return em[index+1:], nil
+}
+
+// nonZeroRandomBytes fills the given slice with non-zero random octets.
+func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) {
+	_, err = io.ReadFull(rand, s)
+	if err != nil {
+		return
+	}
+
+	for i := 0; i < len(s); i++ {
+		for s[i] == 0 {
+			_, err = io.ReadFull(rand, s[i:i+1])
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	return
+}
diff --git a/openpgp/elgamal/elgamal_test.go b/openpgp/elgamal/elgamal_test.go
new file mode 100644
index 0000000..c4f99f5
--- /dev/null
+++ b/openpgp/elgamal/elgamal_test.go
@@ -0,0 +1,49 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package elgamal
+
+import (
+	"bytes"
+	"crypto/rand"
+	"math/big"
+	"testing"
+)
+
+// This is the 1024-bit MODP group from RFC 5114, section 2.1:
+const primeHex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371"
+
+const generatorHex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5"
+
+func fromHex(hex string) *big.Int {
+	n, ok := new(big.Int).SetString(hex, 16)
+	if !ok {
+		panic("failed to parse hex number")
+	}
+	return n
+}
+
+func TestEncryptDecrypt(t *testing.T) {
+	priv := &PrivateKey{
+		PublicKey: PublicKey{
+			G: fromHex(generatorHex),
+			P: fromHex(primeHex),
+		},
+		X: fromHex("42"),
+	}
+	priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P)
+
+	message := []byte("hello world")
+	c1, c2, err := Encrypt(rand.Reader, &priv.PublicKey, message)
+	if err != nil {
+		t.Errorf("error encrypting: %s", err)
+	}
+	message2, err := Decrypt(priv, c1, c2)
+	if err != nil {
+		t.Errorf("error decrypting: %s", err)
+	}
+	if !bytes.Equal(message2, message) {
+		t.Errorf("decryption failed, got: %x, want: %x", message2, message)
+	}
+}
diff --git a/openpgp/errors/errors.go b/openpgp/errors/errors.go
new file mode 100644
index 0000000..b9016bd
--- /dev/null
+++ b/openpgp/errors/errors.go
@@ -0,0 +1,64 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package errors contains common error types for the OpenPGP packages.
+package errors
+
+import (
+	"strconv"
+)
+
+// A StructuralError is returned when OpenPGP data is found to be syntactically
+// invalid.
+type StructuralError string
+
+func (s StructuralError) Error() string {
+	return "OpenPGP data invalid: " + string(s)
+}
+
+// UnsupportedError indicates that, although the OpenPGP data is valid, it
+// makes use of currently unimplemented features.
+type UnsupportedError string
+
+func (s UnsupportedError) Error() string {
+	return "OpenPGP feature unsupported: " + string(s)
+}
+
+// InvalidArgumentError indicates that the caller is in error and passed an
+// incorrect value.
+type InvalidArgumentError string
+
+func (i InvalidArgumentError) Error() string {
+	return "OpenPGP argument invalid: " + string(i)
+}
+
+// SignatureError indicates that a syntactically valid signature failed to
+// validate.
+type SignatureError string
+
+func (b SignatureError) Error() string {
+	return "OpenPGP signature invalid: " + string(b)
+}
+
+type keyIncorrectError int
+
+func (ki keyIncorrectError) Error() string {
+	return "the given key was incorrect"
+}
+
+var ErrKeyIncorrect error = keyIncorrectError(0)
+
+type unknownIssuerError int
+
+func (unknownIssuerError) Error() string {
+	return "signature make by unknown entity"
+}
+
+var ErrUnknownIssuer error = unknownIssuerError(0)
+
+type UnknownPacketTypeError uint8
+
+func (upte UnknownPacketTypeError) Error() string {
+	return "unknown OpenPGP packet type: " + strconv.Itoa(int(upte))
+}
diff --git a/openpgp/keys.go b/openpgp/keys.go
new file mode 100644
index 0000000..5ae8fbd
--- /dev/null
+++ b/openpgp/keys.go
@@ -0,0 +1,546 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"code.google.com/p/go.crypto/openpgp/armor"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/packet"
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"io"
+	"time"
+)
+
+// PublicKeyType is the armor type for a PGP public key.
+var PublicKeyType = "PGP PUBLIC KEY BLOCK"
+
+// PrivateKeyType is the armor type for a PGP private key.
+var PrivateKeyType = "PGP PRIVATE KEY BLOCK"
+
+// An Entity represents the components of an OpenPGP key: a primary public key
+// (which must be a signing key), one or more identities claimed by that key,
+// and zero or more subkeys, which may be encryption keys.
+type Entity struct {
+	PrimaryKey *packet.PublicKey
+	PrivateKey *packet.PrivateKey
+	Identities map[string]*Identity // indexed by Identity.Name
+	Subkeys    []Subkey
+}
+
+// An Identity represents an identity claimed by an Entity and zero or more
+// assertions by other entities about that claim.
+type Identity struct {
+	Name          string // by convention, has the form "Full Name (comment) <email@example.com>"
+	UserId        *packet.UserId
+	SelfSignature *packet.Signature
+	Signatures    []*packet.Signature
+}
+
+// A Subkey is an additional public key in an Entity. Subkeys can be used for
+// encryption.
+type Subkey struct {
+	PublicKey  *packet.PublicKey
+	PrivateKey *packet.PrivateKey
+	Sig        *packet.Signature
+}
+
+// A Key identifies a specific public key in an Entity. This is either the
+// Entity's primary key or a subkey.
+type Key struct {
+	Entity        *Entity
+	PublicKey     *packet.PublicKey
+	PrivateKey    *packet.PrivateKey
+	SelfSignature *packet.Signature
+}
+
+// A KeyRing provides access to public and private keys.
+type KeyRing interface {
+	// KeysById returns the set of keys that have the given key id.
+	KeysById(id uint64) []Key
+	// DecryptionKeys returns all private keys that are valid for
+	// decryption.
+	DecryptionKeys() []Key
+}
+
+// primaryIdentity returns the Identity marked as primary or the first identity
+// if none are so marked.
+func (e *Entity) primaryIdentity() *Identity {
+	var firstIdentity *Identity
+	for _, ident := range e.Identities {
+		if firstIdentity == nil {
+			firstIdentity = ident
+		}
+		if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+			return ident
+		}
+	}
+	return firstIdentity
+}
+
+// encryptionKey returns the best candidate Key for encrypting a message to the
+// given Entity.
+func (e *Entity) encryptionKey() Key {
+	candidateSubkey := -1
+
+	for i, subkey := range e.Subkeys {
+		if subkey.Sig.FlagsValid && subkey.Sig.FlagEncryptCommunications && subkey.PublicKey.PubKeyAlgo.CanEncrypt() {
+			candidateSubkey = i
+			break
+		}
+	}
+
+	i := e.primaryIdentity()
+
+	if e.PrimaryKey.PubKeyAlgo.CanEncrypt() {
+		// If we don't have any candidate subkeys for encryption and
+		// the primary key doesn't have any usage metadata then we
+		// assume that the primary key is ok. Or, if the primary key is
+		// marked as ok to encrypt to, then we can obviously use it.
+		if candidateSubkey == -1 && !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications && i.SelfSignature.FlagsValid {
+			return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}
+		}
+	}
+
+	if candidateSubkey != -1 {
+		subkey := e.Subkeys[candidateSubkey]
+		return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}
+	}
+
+	// This Entity appears to be signing only.
+	return Key{}
+}
+
+// signingKey return the best candidate Key for signing a message with this
+// Entity.
+func (e *Entity) signingKey() Key {
+	candidateSubkey := -1
+
+	for i, subkey := range e.Subkeys {
+		if subkey.Sig.FlagsValid && subkey.Sig.FlagSign && subkey.PublicKey.PubKeyAlgo.CanSign() {
+			candidateSubkey = i
+			break
+		}
+	}
+
+	i := e.primaryIdentity()
+
+	// If we have no candidate subkey then we assume that it's ok to sign
+	// with the primary key.
+	if candidateSubkey == -1 || i.SelfSignature.FlagsValid && i.SelfSignature.FlagSign {
+		return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}
+	}
+
+	subkey := e.Subkeys[candidateSubkey]
+	return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}
+}
+
+// An EntityList contains one or more Entities.
+type EntityList []*Entity
+
+// KeysById returns the set of keys that have the given key id.
+func (el EntityList) KeysById(id uint64) (keys []Key) {
+	for _, e := range el {
+		if e.PrimaryKey.KeyId == id {
+			var selfSig *packet.Signature
+			for _, ident := range e.Identities {
+				if selfSig == nil {
+					selfSig = ident.SelfSignature
+				} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
+					selfSig = ident.SelfSignature
+					break
+				}
+			}
+			keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig})
+		}
+
+		for _, subKey := range e.Subkeys {
+			if subKey.PublicKey.KeyId == id {
+				keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+			}
+		}
+	}
+	return
+}
+
+// DecryptionKeys returns all private keys that are valid for decryption.
+func (el EntityList) DecryptionKeys() (keys []Key) {
+	for _, e := range el {
+		for _, subKey := range e.Subkeys {
+			if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
+				keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig})
+			}
+		}
+	}
+	return
+}
+
+// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file.
+func ReadArmoredKeyRing(r io.Reader) (EntityList, error) {
+	block, err := armor.Decode(r)
+	if err == io.EOF {
+		return nil, errors.InvalidArgumentError("no armored data found")
+	}
+	if err != nil {
+		return nil, err
+	}
+	if block.Type != PublicKeyType && block.Type != PrivateKeyType {
+		return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type)
+	}
+
+	return ReadKeyRing(block.Body)
+}
+
+// ReadKeyRing reads one or more public/private keys. Unsupported keys are
+// ignored as long as at least a single valid key is found.
+func ReadKeyRing(r io.Reader) (el EntityList, err error) {
+	packets := packet.NewReader(r)
+	var lastUnsupportedError error
+
+	for {
+		var e *Entity
+		e, err = readEntity(packets)
+		if err != nil {
+			if _, ok := err.(errors.UnsupportedError); ok {
+				lastUnsupportedError = err
+				err = readToNextPublicKey(packets)
+			}
+			if err == io.EOF {
+				err = nil
+				break
+			}
+			if err != nil {
+				el = nil
+				break
+			}
+		} else {
+			el = append(el, e)
+		}
+	}
+
+	if len(el) == 0 && err == nil {
+		err = lastUnsupportedError
+	}
+	return
+}
+
+// readToNextPublicKey reads packets until the start of the entity and leaves
+// the first packet of the new entity in the Reader.
+func readToNextPublicKey(packets *packet.Reader) (err error) {
+	var p packet.Packet
+	for {
+		p, err = packets.Next()
+		if err == io.EOF {
+			return
+		} else if err != nil {
+			if _, ok := err.(errors.UnsupportedError); ok {
+				err = nil
+				continue
+			}
+			return
+		}
+
+		if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey {
+			packets.Unread(p)
+			return
+		}
+	}
+
+	panic("unreachable")
+}
+
+// readEntity reads an entity (public key, identities, subkeys etc) from the
+// given Reader.
+func readEntity(packets *packet.Reader) (*Entity, error) {
+	e := new(Entity)
+	e.Identities = make(map[string]*Identity)
+
+	p, err := packets.Next()
+	if err != nil {
+		return nil, err
+	}
+
+	var ok bool
+	if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok {
+		if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok {
+			packets.Unread(p)
+			return nil, errors.StructuralError("first packet was not a public/private key")
+		} else {
+			e.PrimaryKey = &e.PrivateKey.PublicKey
+		}
+	}
+
+	if !e.PrimaryKey.PubKeyAlgo.CanSign() {
+		return nil, errors.StructuralError("primary key cannot be used for signatures")
+	}
+
+	var current *Identity
+EachPacket:
+	for {
+		p, err := packets.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return nil, err
+		}
+
+		switch pkt := p.(type) {
+		case *packet.UserId:
+			current = new(Identity)
+			current.Name = pkt.Id
+			current.UserId = pkt
+			e.Identities[pkt.Id] = current
+
+			for {
+				p, err = packets.Next()
+				if err == io.EOF {
+					return nil, io.ErrUnexpectedEOF
+				} else if err != nil {
+					return nil, err
+				}
+
+				sig, ok := p.(*packet.Signature)
+				if !ok {
+					return nil, errors.StructuralError("user ID packet not followed by self-signature")
+				}
+
+				if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId {
+					if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, sig); err != nil {
+						return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error())
+					}
+					current.SelfSignature = sig
+					break
+				}
+				current.Signatures = append(current.Signatures, sig)
+			}
+		case *packet.Signature:
+			if current == nil {
+				return nil, errors.StructuralError("signature packet found before user id packet")
+			}
+			current.Signatures = append(current.Signatures, pkt)
+		case *packet.PrivateKey:
+			if pkt.IsSubkey == false {
+				packets.Unread(p)
+				break EachPacket
+			}
+			err = addSubkey(e, packets, &pkt.PublicKey, pkt)
+			if err != nil {
+				return nil, err
+			}
+		case *packet.PublicKey:
+			if pkt.IsSubkey == false {
+				packets.Unread(p)
+				break EachPacket
+			}
+			err = addSubkey(e, packets, pkt, nil)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			// we ignore unknown packets
+		}
+	}
+
+	if len(e.Identities) == 0 {
+		return nil, errors.StructuralError("entity without any identities")
+	}
+
+	return e, nil
+}
+
+func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error {
+	var subKey Subkey
+	subKey.PublicKey = pub
+	subKey.PrivateKey = priv
+	p, err := packets.Next()
+	if err == io.EOF {
+		return io.ErrUnexpectedEOF
+	}
+	if err != nil {
+		return errors.StructuralError("subkey signature invalid: " + err.Error())
+	}
+	var ok bool
+	subKey.Sig, ok = p.(*packet.Signature)
+	if !ok {
+		return errors.StructuralError("subkey packet not followed by signature")
+	}
+	if subKey.Sig.SigType != packet.SigTypeSubkeyBinding {
+		return errors.StructuralError("subkey signature with wrong type")
+	}
+	err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig)
+	if err != nil {
+		return errors.StructuralError("subkey signature invalid: " + err.Error())
+	}
+	e.Subkeys = append(e.Subkeys, subKey)
+	return nil
+}
+
+const defaultRSAKeyBits = 2048
+
+// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a
+// single identity composed of the given full name, comment and email, any of
+// which may be empty but must not contain any of "()<>\x00".
+func NewEntity(rand io.Reader, currentTime time.Time, name, comment, email string) (*Entity, error) {
+	uid := packet.NewUserId(name, comment, email)
+	if uid == nil {
+		return nil, errors.InvalidArgumentError("user id field contained invalid characters")
+	}
+	signingPriv, err := rsa.GenerateKey(rand, defaultRSAKeyBits)
+	if err != nil {
+		return nil, err
+	}
+	encryptingPriv, err := rsa.GenerateKey(rand, defaultRSAKeyBits)
+	if err != nil {
+		return nil, err
+	}
+
+	e := &Entity{
+		PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey),
+		PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv),
+		Identities: make(map[string]*Identity),
+	}
+	isPrimaryId := true
+	e.Identities[uid.Id] = &Identity{
+		Name:   uid.Name,
+		UserId: uid,
+		SelfSignature: &packet.Signature{
+			CreationTime: currentTime,
+			SigType:      packet.SigTypePositiveCert,
+			PubKeyAlgo:   packet.PubKeyAlgoRSA,
+			Hash:         crypto.SHA256,
+			IsPrimaryId:  &isPrimaryId,
+			FlagsValid:   true,
+			FlagSign:     true,
+			FlagCertify:  true,
+			IssuerKeyId:  &e.PrimaryKey.KeyId,
+		},
+	}
+
+	e.Subkeys = make([]Subkey, 1)
+	e.Subkeys[0] = Subkey{
+		PublicKey:  packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey),
+		PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv),
+		Sig: &packet.Signature{
+			CreationTime:              currentTime,
+			SigType:                   packet.SigTypeSubkeyBinding,
+			PubKeyAlgo:                packet.PubKeyAlgoRSA,
+			Hash:                      crypto.SHA256,
+			FlagsValid:                true,
+			FlagEncryptStorage:        true,
+			FlagEncryptCommunications: true,
+			IssuerKeyId:               &e.PrimaryKey.KeyId,
+		},
+	}
+	e.Subkeys[0].PublicKey.IsSubkey = true
+	e.Subkeys[0].PrivateKey.IsSubkey = true
+
+	return e, nil
+}
+
+// SerializePrivate serializes an Entity, including private key material, to
+// the given Writer. For now, it must only be used on an Entity returned from
+// NewEntity.
+func (e *Entity) SerializePrivate(w io.Writer) (err error) {
+	err = e.PrivateKey.Serialize(w)
+	if err != nil {
+		return
+	}
+	for _, ident := range e.Identities {
+		err = ident.UserId.Serialize(w)
+		if err != nil {
+			return
+		}
+		err = ident.SelfSignature.SignUserId(rand.Reader, ident.UserId.Id, e.PrimaryKey, e.PrivateKey)
+		if err != nil {
+			return
+		}
+		err = ident.SelfSignature.Serialize(w)
+		if err != nil {
+			return
+		}
+	}
+	for _, subkey := range e.Subkeys {
+		err = subkey.PrivateKey.Serialize(w)
+		if err != nil {
+			return
+		}
+		err = subkey.Sig.SignKey(rand.Reader, subkey.PublicKey, e.PrivateKey)
+		if err != nil {
+			return
+		}
+		err = subkey.Sig.Serialize(w)
+		if err != nil {
+			return
+		}
+	}
+	return nil
+}
+
+// Serialize writes the public part of the given Entity to w. (No private
+// key material will be output).
+func (e *Entity) Serialize(w io.Writer) error {
+	err := e.PrimaryKey.Serialize(w)
+	if err != nil {
+		return err
+	}
+	for _, ident := range e.Identities {
+		err = ident.UserId.Serialize(w)
+		if err != nil {
+			return err
+		}
+		err = ident.SelfSignature.Serialize(w)
+		if err != nil {
+			return err
+		}
+		for _, sig := range ident.Signatures {
+			err = sig.Serialize(w)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	for _, subkey := range e.Subkeys {
+		err = subkey.PublicKey.Serialize(w)
+		if err != nil {
+			return err
+		}
+		err = subkey.Sig.Serialize(w)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// SignIdentity adds a signature to e, from signer, attesting that identity is
+// associated with e. The provided identity must already be an element of
+// e.Identities and the private key of signer must have been decrypted if
+// necessary.
+func (e *Entity) SignIdentity(identity string, signer *Entity) error {
+	if signer.PrivateKey == nil {
+		return errors.InvalidArgumentError("signing Entity must have a private key")
+	}
+	if signer.PrivateKey.Encrypted {
+		return errors.InvalidArgumentError("signing Entity's private key must be decrypted")
+	}
+	ident, ok := e.Identities[identity]
+	if !ok {
+		return errors.InvalidArgumentError("given identity string not found in Entity")
+	}
+
+	sig := &packet.Signature{
+		SigType:      packet.SigTypeGenericCert,
+		PubKeyAlgo:   signer.PrivateKey.PubKeyAlgo,
+		Hash:         crypto.SHA256,
+		CreationTime: time.Now(),
+		IssuerKeyId:  &signer.PrivateKey.KeyId,
+	}
+	if err := sig.SignKey(rand.Reader, e.PrimaryKey, signer.PrivateKey); err != nil {
+		return err
+	}
+	ident.Signatures = append(ident.Signatures, sig)
+	return nil
+}
diff --git a/openpgp/packet/compressed.go b/openpgp/packet/compressed.go
new file mode 100644
index 0000000..816229e
--- /dev/null
+++ b/openpgp/packet/compressed.go
@@ -0,0 +1,38 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"compress/flate"
+	"compress/zlib"
+	"io"
+	"strconv"
+)
+
+// Compressed represents a compressed OpenPGP packet. The decompressed contents
+// will contain more OpenPGP packets. See RFC 4880, section 5.6.
+type Compressed struct {
+	Body io.Reader
+}
+
+func (c *Compressed) parse(r io.Reader) error {
+	var buf [1]byte
+	_, err := readFull(r, buf[:])
+	if err != nil {
+		return err
+	}
+
+	switch buf[0] {
+	case 1:
+		c.Body = flate.NewReader(r)
+	case 2:
+		c.Body, err = zlib.NewReader(r)
+	default:
+		err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0])))
+	}
+
+	return err
+}
diff --git a/openpgp/packet/compressed_test.go b/openpgp/packet/compressed_test.go
new file mode 100644
index 0000000..cb2d70b
--- /dev/null
+++ b/openpgp/packet/compressed_test.go
@@ -0,0 +1,41 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+func TestCompressed(t *testing.T) {
+	packet, err := Read(readerFromHex(compressedHex))
+	if err != nil {
+		t.Errorf("failed to read Compressed: %s", err)
+		return
+	}
+
+	c, ok := packet.(*Compressed)
+	if !ok {
+		t.Error("didn't find Compressed packet")
+		return
+	}
+
+	contents, err := ioutil.ReadAll(c.Body)
+	if err != nil && err != io.EOF {
+		t.Error(err)
+		return
+	}
+
+	expected, _ := hex.DecodeString(compressedExpectedHex)
+	if !bytes.Equal(expected, contents) {
+		t.Errorf("got:%x want:%x", contents, expected)
+	}
+}
+
+const compressedHex = "a3013b2d90c4e02b72e25f727e5e496a5e49b11e1700"
+const compressedExpectedHex = "cb1062004d14c8fe636f6e74656e74732e0a"
diff --git a/openpgp/packet/encrypted_key.go b/openpgp/packet/encrypted_key.go
new file mode 100644
index 0000000..b4a4eff
--- /dev/null
+++ b/openpgp/packet/encrypted_key.go
@@ -0,0 +1,167 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/elgamal"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto/rand"
+	"crypto/rsa"
+	"encoding/binary"
+	"io"
+	"math/big"
+	"strconv"
+)
+
+const encryptedKeyVersion = 3
+
+// EncryptedKey represents a public-key encrypted session key. See RFC 4880,
+// section 5.1.
+type EncryptedKey struct {
+	KeyId      uint64
+	Algo       PublicKeyAlgorithm
+	CipherFunc CipherFunction // only valid after a successful Decrypt
+	Key        []byte         // only valid after a successful Decrypt
+
+	encryptedMPI1, encryptedMPI2 []byte
+}
+
+func (e *EncryptedKey) parse(r io.Reader) (err error) {
+	var buf [10]byte
+	_, err = readFull(r, buf[:])
+	if err != nil {
+		return
+	}
+	if buf[0] != encryptedKeyVersion {
+		return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0])))
+	}
+	e.KeyId = binary.BigEndian.Uint64(buf[1:9])
+	e.Algo = PublicKeyAlgorithm(buf[9])
+	switch e.Algo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+		e.encryptedMPI1, _, err = readMPI(r)
+	case PubKeyAlgoElGamal:
+		e.encryptedMPI1, _, err = readMPI(r)
+		if err != nil {
+			return
+		}
+		e.encryptedMPI2, _, err = readMPI(r)
+	}
+	_, err = consumeAll(r)
+	return
+}
+
+func checksumKeyMaterial(key []byte) uint16 {
+	var checksum uint16
+	for _, v := range key {
+		checksum += uint16(v)
+	}
+	return checksum
+}
+
+// Decrypt decrypts an encrypted session key with the given private key. The
+// private key must have been decrypted first.
+func (e *EncryptedKey) Decrypt(priv *PrivateKey) error {
+	var err error
+	var b []byte
+
+	// TODO(agl): use session key decryption routines here to avoid
+	// padding oracle attacks.
+	switch priv.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+		b, err = rsa.DecryptPKCS1v15(rand.Reader, priv.PrivateKey.(*rsa.PrivateKey), e.encryptedMPI1)
+	case PubKeyAlgoElGamal:
+		c1 := new(big.Int).SetBytes(e.encryptedMPI1)
+		c2 := new(big.Int).SetBytes(e.encryptedMPI2)
+		b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2)
+	default:
+		err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo)))
+	}
+
+	if err != nil {
+		return err
+	}
+
+	e.CipherFunc = CipherFunction(b[0])
+	e.Key = b[1 : len(b)-2]
+	expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
+	checksum := checksumKeyMaterial(e.Key)
+	if checksum != expectedChecksum {
+		return errors.StructuralError("EncryptedKey checksum incorrect")
+	}
+
+	return nil
+}
+
+// SerializeEncryptedKey serializes an encrypted key packet to w that contains
+// key, encrypted to pub.
+func SerializeEncryptedKey(w io.Writer, rand io.Reader, pub *PublicKey, cipherFunc CipherFunction, key []byte) error {
+	var buf [10]byte
+	buf[0] = encryptedKeyVersion
+	binary.BigEndian.PutUint64(buf[1:9], pub.KeyId)
+	buf[9] = byte(pub.PubKeyAlgo)
+
+	keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */ )
+	keyBlock[0] = byte(cipherFunc)
+	copy(keyBlock[1:], key)
+	checksum := checksumKeyMaterial(key)
+	keyBlock[1+len(key)] = byte(checksum >> 8)
+	keyBlock[1+len(key)+1] = byte(checksum)
+
+	switch pub.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly:
+		return serializeEncryptedKeyRSA(w, rand, buf, pub.PublicKey.(*rsa.PublicKey), keyBlock)
+	case PubKeyAlgoElGamal:
+		return serializeEncryptedKeyElGamal(w, rand, buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock)
+	case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly:
+		return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+	}
+
+	return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo)))
+}
+
+func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error {
+	cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)
+	if err != nil {
+		return errors.InvalidArgumentError("RSA encryption failed: " + err.Error())
+	}
+
+	packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText)
+
+	err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(header[:])
+	if err != nil {
+		return err
+	}
+	return writeMPI(w, 8*uint16(len(cipherText)), cipherText)
+}
+
+func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error {
+	c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock)
+	if err != nil {
+		return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error())
+	}
+
+	packetLen := 10 /* header length */
+	packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8
+	packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8
+
+	err = serializeHeader(w, packetTypeEncryptedKey, packetLen)
+	if err != nil {
+		return err
+	}
+	_, err = w.Write(header[:])
+	if err != nil {
+		return err
+	}
+	err = writeBig(w, c1)
+	if err != nil {
+		return err
+	}
+	return writeBig(w, c2)
+}
diff --git a/openpgp/packet/encrypted_key_test.go b/openpgp/packet/encrypted_key_test.go
new file mode 100644
index 0000000..2b8eff7
--- /dev/null
+++ b/openpgp/packet/encrypted_key_test.go
@@ -0,0 +1,126 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/rsa"
+	"fmt"
+	"math/big"
+	"testing"
+)
+
+func bigFromBase10(s string) *big.Int {
+	b, ok := new(big.Int).SetString(s, 10)
+	if !ok {
+		panic("bigFromBase10 failed")
+	}
+	return b
+}
+
+var encryptedKeyPub = rsa.PublicKey{
+	E: 65537,
+	N: bigFromBase10("115804063926007623305902631768113868327816898845124614648849934718568541074358183759250136204762053879858102352159854352727097033322663029387610959884180306668628526686121021235757016368038585212410610742029286439607686208110250133174279811431933746643015923132833417396844716207301518956640020862630546868823"),
+}
+
+var encryptedKeyRSAPriv = &rsa.PrivateKey{
+	PublicKey: encryptedKeyPub,
+	D:         bigFromBase10("32355588668219869544751561565313228297765464314098552250409557267371233892496951383426602439009993875125222579159850054973310859166139474359774543943714622292329487391199285040721944491839695981199720170366763547754915493640685849961780092241140181198779299712578774460837139360803883139311171713302987058393"),
+}
+
+var encryptedKeyPriv = &PrivateKey{
+	PublicKey: PublicKey{
+		PubKeyAlgo: PubKeyAlgoRSA,
+	},
+	PrivateKey: encryptedKeyRSAPriv,
+}
+
+func TestDecryptingEncryptedKey(t *testing.T) {
+	const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8"
+	const expectedKeyHex = "d930363f7e0308c333b9618617ea728963d8df993665ae7be1092d4926fd864b"
+
+	p, err := Read(readerFromHex(encryptedKeyHex))
+	if err != nil {
+		t.Errorf("error from Read: %s", err)
+		return
+	}
+	ek, ok := p.(*EncryptedKey)
+	if !ok {
+		t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+		return
+	}
+
+	if ek.KeyId != 0x2a67d68660df41c7 || ek.Algo != PubKeyAlgoRSA {
+		t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+		return
+	}
+
+	err = ek.Decrypt(encryptedKeyPriv)
+	if err != nil {
+		t.Errorf("error from Decrypt: %s", err)
+		return
+	}
+
+	if ek.CipherFunc != CipherAES256 {
+		t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+		return
+	}
+
+	keyHex := fmt.Sprintf("%x", ek.Key)
+	if keyHex != expectedKeyHex {
+		t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex)
+	}
+}
+
+func TestEncryptingEncryptedKey(t *testing.T) {
+	key := []byte{1, 2, 3, 4}
+	const expectedKeyHex = "01020304"
+	const keyId = 42
+
+	pub := &PublicKey{
+		PublicKey:  &encryptedKeyPub,
+		KeyId:      keyId,
+		PubKeyAlgo: PubKeyAlgoRSAEncryptOnly,
+	}
+
+	buf := new(bytes.Buffer)
+	err := SerializeEncryptedKey(buf, rand.Reader, pub, CipherAES128, key)
+	if err != nil {
+		t.Errorf("error writing encrypted key packet: %s", err)
+	}
+
+	p, err := Read(buf)
+	if err != nil {
+		t.Errorf("error from Read: %s", err)
+		return
+	}
+	ek, ok := p.(*EncryptedKey)
+	if !ok {
+		t.Errorf("didn't parse an EncryptedKey, got %#v", p)
+		return
+	}
+
+	if ek.KeyId != keyId || ek.Algo != PubKeyAlgoRSAEncryptOnly {
+		t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+		return
+	}
+
+	err = ek.Decrypt(encryptedKeyPriv)
+	if err != nil {
+		t.Errorf("error from Decrypt: %s", err)
+		return
+	}
+
+	if ek.CipherFunc != CipherAES128 {
+		t.Errorf("unexpected EncryptedKey contents: %#v", ek)
+		return
+	}
+
+	keyHex := fmt.Sprintf("%x", ek.Key)
+	if keyHex != expectedKeyHex {
+		t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex)
+	}
+}
diff --git a/openpgp/packet/literal.go b/openpgp/packet/literal.go
new file mode 100644
index 0000000..1a9ec6e
--- /dev/null
+++ b/openpgp/packet/literal.go
@@ -0,0 +1,89 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"encoding/binary"
+	"io"
+)
+
+// LiteralData represents an encrypted file. See RFC 4880, section 5.9.
+type LiteralData struct {
+	IsBinary bool
+	FileName string
+	Time     uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined.
+	Body     io.Reader
+}
+
+// ForEyesOnly returns whether the contents of the LiteralData have been marked
+// as especially sensitive.
+func (l *LiteralData) ForEyesOnly() bool {
+	return l.FileName == "_CONSOLE"
+}
+
+func (l *LiteralData) parse(r io.Reader) (err error) {
+	var buf [256]byte
+
+	_, err = readFull(r, buf[:2])
+	if err != nil {
+		return
+	}
+
+	l.IsBinary = buf[0] == 'b'
+	fileNameLen := int(buf[1])
+
+	_, err = readFull(r, buf[:fileNameLen])
+	if err != nil {
+		return
+	}
+
+	l.FileName = string(buf[:fileNameLen])
+
+	_, err = readFull(r, buf[:4])
+	if err != nil {
+		return
+	}
+
+	l.Time = binary.BigEndian.Uint32(buf[:4])
+	l.Body = r
+	return
+}
+
+// SerializeLiteral serializes a literal data packet to w and returns a
+// WriteCloser to which the data itself can be written and which MUST be closed
+// on completion. The fileName is truncated to 255 bytes.
+func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) {
+	var buf [4]byte
+	buf[0] = 't'
+	if isBinary {
+		buf[0] = 'b'
+	}
+	if len(fileName) > 255 {
+		fileName = fileName[:255]
+	}
+	buf[1] = byte(len(fileName))
+
+	inner, err := serializeStreamHeader(w, packetTypeLiteralData)
+	if err != nil {
+		return
+	}
+
+	_, err = inner.Write(buf[:2])
+	if err != nil {
+		return
+	}
+	_, err = inner.Write([]byte(fileName))
+	if err != nil {
+		return
+	}
+	binary.BigEndian.PutUint32(buf[:], time)
+	_, err = inner.Write(buf[:])
+	if err != nil {
+		return
+	}
+
+	plaintext = inner
+	return
+}
diff --git a/openpgp/packet/one_pass_signature.go b/openpgp/packet/one_pass_signature.go
new file mode 100644
index 0000000..8b5c547
--- /dev/null
+++ b/openpgp/packet/one_pass_signature.go
@@ -0,0 +1,73 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+	"crypto"
+	"encoding/binary"
+	"io"
+	"strconv"
+)
+
+// OnePassSignature represents a one-pass signature packet. See RFC 4880,
+// section 5.4.
+type OnePassSignature struct {
+	SigType    SignatureType
+	Hash       crypto.Hash
+	PubKeyAlgo PublicKeyAlgorithm
+	KeyId      uint64
+	IsLast     bool
+}
+
+const onePassSignatureVersion = 3
+
+func (ops *OnePassSignature) parse(r io.Reader) (err error) {
+	var buf [13]byte
+
+	_, err = readFull(r, buf[:])
+	if err != nil {
+		return
+	}
+	if buf[0] != onePassSignatureVersion {
+		err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0])))
+	}
+
+	var ok bool
+	ops.Hash, ok = s2k.HashIdToHash(buf[2])
+	if !ok {
+		return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
+	}
+
+	ops.SigType = SignatureType(buf[1])
+	ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3])
+	ops.KeyId = binary.BigEndian.Uint64(buf[4:12])
+	ops.IsLast = buf[12] != 0
+	return
+}
+
+// Serialize marshals the given OnePassSignature to w.
+func (ops *OnePassSignature) Serialize(w io.Writer) error {
+	var buf [13]byte
+	buf[0] = onePassSignatureVersion
+	buf[1] = uint8(ops.SigType)
+	var ok bool
+	buf[2], ok = s2k.HashToHashId(ops.Hash)
+	if !ok {
+		return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
+	}
+	buf[3] = uint8(ops.PubKeyAlgo)
+	binary.BigEndian.PutUint64(buf[4:12], ops.KeyId)
+	if ops.IsLast {
+		buf[12] = 1
+	}
+
+	if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil {
+		return err
+	}
+	_, err := w.Write(buf[:])
+	return err
+}
diff --git a/openpgp/packet/packet.go b/openpgp/packet/packet.go
new file mode 100644
index 0000000..3f858f6
--- /dev/null
+++ b/openpgp/packet/packet.go
@@ -0,0 +1,482 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package packet implements parsing and serialization of OpenPGP packets, as
+// specified in RFC 4880.
+package packet
+
+import (
+	"code.google.com/p/go.crypto/cast5"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto/aes"
+	"crypto/cipher"
+	"io"
+	"math/big"
+)
+
+// readFull is the same as io.ReadFull except that reading zero bytes returns
+// ErrUnexpectedEOF rather than EOF.
+func readFull(r io.Reader, buf []byte) (n int, err error) {
+	n, err = io.ReadFull(r, buf)
+	if err == io.EOF {
+		err = io.ErrUnexpectedEOF
+	}
+	return
+}
+
+// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2.
+func readLength(r io.Reader) (length int64, isPartial bool, err error) {
+	var buf [4]byte
+	_, err = readFull(r, buf[:1])
+	if err != nil {
+		return
+	}
+	switch {
+	case buf[0] < 192:
+		length = int64(buf[0])
+	case buf[0] < 224:
+		length = int64(buf[0]-192) << 8
+		_, err = readFull(r, buf[0:1])
+		if err != nil {
+			return
+		}
+		length += int64(buf[0]) + 192
+	case buf[0] < 255:
+		length = int64(1) << (buf[0] & 0x1f)
+		isPartial = true
+	default:
+		_, err = readFull(r, buf[0:4])
+		if err != nil {
+			return
+		}
+		length = int64(buf[0])<<24 |
+			int64(buf[1])<<16 |
+			int64(buf[2])<<8 |
+			int64(buf[3])
+	}
+	return
+}
+
+// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths.
+// The continuation lengths are parsed and removed from the stream and EOF is
+// returned at the end of the packet. See RFC 4880, section 4.2.2.4.
+type partialLengthReader struct {
+	r         io.Reader
+	remaining int64
+	isPartial bool
+}
+
+func (r *partialLengthReader) Read(p []byte) (n int, err error) {
+	for r.remaining == 0 {
+		if !r.isPartial {
+			return 0, io.EOF
+		}
+		r.remaining, r.isPartial, err = readLength(r.r)
+		if err != nil {
+			return 0, err
+		}
+	}
+
+	toRead := int64(len(p))
+	if toRead > r.remaining {
+		toRead = r.remaining
+	}
+
+	n, err = r.r.Read(p[:int(toRead)])
+	r.remaining -= int64(n)
+	if n < int(toRead) && err == io.EOF {
+		err = io.ErrUnexpectedEOF
+	}
+	return
+}
+
+// partialLengthWriter writes a stream of data using OpenPGP partial lengths.
+// See RFC 4880, section 4.2.2.4.
+type partialLengthWriter struct {
+	w          io.WriteCloser
+	lengthByte [1]byte
+}
+
+func (w *partialLengthWriter) Write(p []byte) (n int, err error) {
+	for len(p) > 0 {
+		for power := uint(14); power < 32; power-- {
+			l := 1 << power
+			if len(p) >= l {
+				w.lengthByte[0] = 224 + uint8(power)
+				_, err = w.w.Write(w.lengthByte[:])
+				if err != nil {
+					return
+				}
+				var m int
+				m, err = w.w.Write(p[:l])
+				n += m
+				if err != nil {
+					return
+				}
+				p = p[l:]
+				break
+			}
+		}
+	}
+	return
+}
+
+func (w *partialLengthWriter) Close() error {
+	w.lengthByte[0] = 0
+	_, err := w.w.Write(w.lengthByte[:])
+	if err != nil {
+		return err
+	}
+	return w.w.Close()
+}
+
+// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the
+// underlying Reader returns EOF before the limit has been reached.
+type spanReader struct {
+	r io.Reader
+	n int64
+}
+
+func (l *spanReader) Read(p []byte) (n int, err error) {
+	if l.n <= 0 {
+		return 0, io.EOF
+	}
+	if int64(len(p)) > l.n {
+		p = p[0:l.n]
+	}
+	n, err = l.r.Read(p)
+	l.n -= int64(n)
+	if l.n > 0 && err == io.EOF {
+		err = io.ErrUnexpectedEOF
+	}
+	return
+}
+
+// readHeader parses a packet header and returns an io.Reader which will return
+// the contents of the packet. See RFC 4880, section 4.2.
+func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) {
+	var buf [4]byte
+	_, err = io.ReadFull(r, buf[:1])
+	if err != nil {
+		return
+	}
+	if buf[0]&0x80 == 0 {
+		err = errors.StructuralError("tag byte does not have MSB set")
+		return
+	}
+	if buf[0]&0x40 == 0 {
+		// Old format packet
+		tag = packetType((buf[0] & 0x3f) >> 2)
+		lengthType := buf[0] & 3
+		if lengthType == 3 {
+			length = -1
+			contents = r
+			return
+		}
+		lengthBytes := 1 << lengthType
+		_, err = readFull(r, buf[0:lengthBytes])
+		if err != nil {
+			return
+		}
+		for i := 0; i < lengthBytes; i++ {
+			length <<= 8
+			length |= int64(buf[i])
+		}
+		contents = &spanReader{r, length}
+		return
+	}
+
+	// New format packet
+	tag = packetType(buf[0] & 0x3f)
+	length, isPartial, err := readLength(r)
+	if err != nil {
+		return
+	}
+	if isPartial {
+		contents = &partialLengthReader{
+			remaining: length,
+			isPartial: true,
+			r:         r,
+		}
+		length = -1
+	} else {
+		contents = &spanReader{r, length}
+	}
+	return
+}
+
+// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section
+// 4.2.
+func serializeHeader(w io.Writer, ptype packetType, length int) (err error) {
+	var buf [6]byte
+	var n int
+
+	buf[0] = 0x80 | 0x40 | byte(ptype)
+	if length < 192 {
+		buf[1] = byte(length)
+		n = 2
+	} else if length < 8384 {
+		length -= 192
+		buf[1] = 192 + byte(length>>8)
+		buf[2] = byte(length)
+		n = 3
+	} else {
+		buf[1] = 255
+		buf[2] = byte(length >> 24)
+		buf[3] = byte(length >> 16)
+		buf[4] = byte(length >> 8)
+		buf[5] = byte(length)
+		n = 6
+	}
+
+	_, err = w.Write(buf[:n])
+	return
+}
+
+// serializeStreamHeader writes an OpenPGP packet header to w where the
+// length of the packet is unknown. It returns a io.WriteCloser which can be
+// used to write the contents of the packet. See RFC 4880, section 4.2.
+func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) {
+	var buf [1]byte
+	buf[0] = 0x80 | 0x40 | byte(ptype)
+	_, err = w.Write(buf[:])
+	if err != nil {
+		return
+	}
+	out = &partialLengthWriter{w: w}
+	return
+}
+
+// Packet represents an OpenPGP packet. Users are expected to try casting
+// instances of this interface to specific packet types.
+type Packet interface {
+	parse(io.Reader) error
+}
+
+// consumeAll reads from the given Reader until error, returning the number of
+// bytes read.
+func consumeAll(r io.Reader) (n int64, err error) {
+	var m int
+	var buf [1024]byte
+
+	for {
+		m, err = r.Read(buf[:])
+		n += int64(m)
+		if err == io.EOF {
+			err = nil
+			return
+		}
+		if err != nil {
+			return
+		}
+	}
+
+	panic("unreachable")
+}
+
+// packetType represents the numeric ids of the different OpenPGP packet types. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2
+type packetType uint8
+
+const (
+	packetTypeEncryptedKey              packetType = 1
+	packetTypeSignature                 packetType = 2
+	packetTypeSymmetricKeyEncrypted     packetType = 3
+	packetTypeOnePassSignature          packetType = 4
+	packetTypePrivateKey                packetType = 5
+	packetTypePublicKey                 packetType = 6
+	packetTypePrivateSubkey             packetType = 7
+	packetTypeCompressed                packetType = 8
+	packetTypeSymmetricallyEncrypted    packetType = 9
+	packetTypeLiteralData               packetType = 11
+	packetTypeUserId                    packetType = 13
+	packetTypePublicSubkey              packetType = 14
+	packetTypeSymmetricallyEncryptedMDC packetType = 18
+)
+
+// Read reads a single OpenPGP packet from the given io.Reader. If there is an
+// error parsing a packet, the whole packet is consumed from the input.
+func Read(r io.Reader) (p Packet, err error) {
+	tag, _, contents, err := readHeader(r)
+	if err != nil {
+		return
+	}
+
+	switch tag {
+	case packetTypeEncryptedKey:
+		p = new(EncryptedKey)
+	case packetTypeSignature:
+		p = new(Signature)
+	case packetTypeSymmetricKeyEncrypted:
+		p = new(SymmetricKeyEncrypted)
+	case packetTypeOnePassSignature:
+		p = new(OnePassSignature)
+	case packetTypePrivateKey, packetTypePrivateSubkey:
+		pk := new(PrivateKey)
+		if tag == packetTypePrivateSubkey {
+			pk.IsSubkey = true
+		}
+		p = pk
+	case packetTypePublicKey, packetTypePublicSubkey:
+		pk := new(PublicKey)
+		if tag == packetTypePublicSubkey {
+			pk.IsSubkey = true
+		}
+		p = pk
+	case packetTypeCompressed:
+		p = new(Compressed)
+	case packetTypeSymmetricallyEncrypted:
+		p = new(SymmetricallyEncrypted)
+	case packetTypeLiteralData:
+		p = new(LiteralData)
+	case packetTypeUserId:
+		p = new(UserId)
+	case packetTypeSymmetricallyEncryptedMDC:
+		se := new(SymmetricallyEncrypted)
+		se.MDC = true
+		p = se
+	default:
+		err = errors.UnknownPacketTypeError(tag)
+	}
+	if p != nil {
+		err = p.parse(contents)
+	}
+	if err != nil {
+		consumeAll(contents)
+	}
+	return
+}
+
+// SignatureType represents the different semantic meanings of an OpenPGP
+// signature. See RFC 4880, section 5.2.1.
+type SignatureType uint8
+
+const (
+	SigTypeBinary        SignatureType = 0
+	SigTypeText                        = 1
+	SigTypeGenericCert                 = 0x10
+	SigTypePersonaCert                 = 0x11
+	SigTypeCasualCert                  = 0x12
+	SigTypePositiveCert                = 0x13
+	SigTypeSubkeyBinding               = 0x18
+)
+
+// PublicKeyAlgorithm represents the different public key system specified for
+// OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12
+type PublicKeyAlgorithm uint8
+
+const (
+	PubKeyAlgoRSA            PublicKeyAlgorithm = 1
+	PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2
+	PubKeyAlgoRSASignOnly    PublicKeyAlgorithm = 3
+	PubKeyAlgoElGamal        PublicKeyAlgorithm = 16
+	PubKeyAlgoDSA            PublicKeyAlgorithm = 17
+)
+
+// CanEncrypt returns true if it's possible to encrypt a message to a public
+// key of the given type.
+func (pka PublicKeyAlgorithm) CanEncrypt() bool {
+	switch pka {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal:
+		return true
+	}
+	return false
+}
+
+// CanSign returns true if it's possible for a public key of the given type to
+// sign a message.
+func (pka PublicKeyAlgorithm) CanSign() bool {
+	switch pka {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+		return true
+	}
+	return false
+}
+
+// CipherFunction represents the different block ciphers specified for OpenPGP. See
+// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13
+type CipherFunction uint8
+
+const (
+	CipherCAST5  CipherFunction = 3
+	CipherAES128 CipherFunction = 7
+	CipherAES192 CipherFunction = 8
+	CipherAES256 CipherFunction = 9
+)
+
+// KeySize returns the key size, in bytes, of cipher.
+func (cipher CipherFunction) KeySize() int {
+	switch cipher {
+	case CipherCAST5:
+		return cast5.KeySize
+	case CipherAES128:
+		return 16
+	case CipherAES192:
+		return 24
+	case CipherAES256:
+		return 32
+	}
+	return 0
+}
+
+// blockSize returns the block size, in bytes, of cipher.
+func (cipher CipherFunction) blockSize() int {
+	switch cipher {
+	case CipherCAST5:
+		return 8
+	case CipherAES128, CipherAES192, CipherAES256:
+		return 16
+	}
+	return 0
+}
+
+// new returns a fresh instance of the given cipher.
+func (cipher CipherFunction) new(key []byte) (block cipher.Block) {
+	switch cipher {
+	case CipherCAST5:
+		block, _ = cast5.NewCipher(key)
+	case CipherAES128, CipherAES192, CipherAES256:
+		block, _ = aes.NewCipher(key)
+	}
+	return
+}
+
+// readMPI reads a big integer from r. The bit length returned is the bit
+// length that was specified in r. This is preserved so that the integer can be
+// reserialized exactly.
+func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) {
+	var buf [2]byte
+	_, err = readFull(r, buf[0:])
+	if err != nil {
+		return
+	}
+	bitLength = uint16(buf[0])<<8 | uint16(buf[1])
+	numBytes := (int(bitLength) + 7) / 8
+	mpi = make([]byte, numBytes)
+	_, err = readFull(r, mpi)
+	return
+}
+
+// mpiLength returns the length of the given *big.Int when serialized as an
+// MPI.
+func mpiLength(n *big.Int) (mpiLengthInBytes int) {
+	mpiLengthInBytes = 2 /* MPI length */
+	mpiLengthInBytes += (n.BitLen() + 7) / 8
+	return
+}
+
+// writeMPI serializes a big integer to w.
+func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) {
+	_, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)})
+	if err == nil {
+		_, err = w.Write(mpiBytes)
+	}
+	return
+}
+
+// writeBig serializes a *big.Int to w.
+func writeBig(w io.Writer, i *big.Int) error {
+	return writeMPI(w, uint16(i.BitLen()), i.Bytes())
+}
diff --git a/openpgp/packet/packet_test.go b/openpgp/packet/packet_test.go
new file mode 100644
index 0000000..c2fd72b
--- /dev/null
+++ b/openpgp/packet/packet_test.go
@@ -0,0 +1,255 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+func TestReadFull(t *testing.T) {
+	var out [4]byte
+
+	b := bytes.NewBufferString("foo")
+	n, err := readFull(b, out[:3])
+	if n != 3 || err != nil {
+		t.Errorf("full read failed n:%d err:%s", n, err)
+	}
+
+	b = bytes.NewBufferString("foo")
+	n, err = readFull(b, out[:4])
+	if n != 3 || err != io.ErrUnexpectedEOF {
+		t.Errorf("partial read failed n:%d err:%s", n, err)
+	}
+
+	b = bytes.NewBuffer(nil)
+	n, err = readFull(b, out[:3])
+	if n != 0 || err != io.ErrUnexpectedEOF {
+		t.Errorf("empty read failed n:%d err:%s", n, err)
+	}
+}
+
+func readerFromHex(s string) io.Reader {
+	data, err := hex.DecodeString(s)
+	if err != nil {
+		panic("readerFromHex: bad input")
+	}
+	return bytes.NewBuffer(data)
+}
+
+var readLengthTests = []struct {
+	hexInput  string
+	length    int64
+	isPartial bool
+	err       error
+}{
+	{"", 0, false, io.ErrUnexpectedEOF},
+	{"1f", 31, false, nil},
+	{"c0", 0, false, io.ErrUnexpectedEOF},
+	{"c101", 256 + 1 + 192, false, nil},
+	{"e0", 1, true, nil},
+	{"e1", 2, true, nil},
+	{"e2", 4, true, nil},
+	{"ff", 0, false, io.ErrUnexpectedEOF},
+	{"ff00", 0, false, io.ErrUnexpectedEOF},
+	{"ff0000", 0, false, io.ErrUnexpectedEOF},
+	{"ff000000", 0, false, io.ErrUnexpectedEOF},
+	{"ff00000000", 0, false, nil},
+	{"ff01020304", 16909060, false, nil},
+}
+
+func TestReadLength(t *testing.T) {
+	for i, test := range readLengthTests {
+		length, isPartial, err := readLength(readerFromHex(test.hexInput))
+		if test.err != nil {
+			if err != test.err {
+				t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("%d: unexpected error: %s", i, err)
+			continue
+		}
+		if length != test.length || isPartial != test.isPartial {
+			t.Errorf("%d: bad result got:(%d,%t) want:(%d,%t)", i, length, isPartial, test.length, test.isPartial)
+		}
+	}
+}
+
+var partialLengthReaderTests = []struct {
+	hexInput  string
+	err       error
+	hexOutput string
+}{
+	{"e0", io.ErrUnexpectedEOF, ""},
+	{"e001", io.ErrUnexpectedEOF, ""},
+	{"e0010102", nil, "0102"},
+	{"ff00000000", nil, ""},
+	{"e10102e1030400", nil, "01020304"},
+	{"e101", io.ErrUnexpectedEOF, ""},
+}
+
+func TestPartialLengthReader(t *testing.T) {
+	for i, test := range partialLengthReaderTests {
+		r := &partialLengthReader{readerFromHex(test.hexInput), 0, true}
+		out, err := ioutil.ReadAll(r)
+		if test.err != nil {
+			if err != test.err {
+				t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("%d: unexpected error: %s", i, err)
+			continue
+		}
+
+		got := fmt.Sprintf("%x", out)
+		if got != test.hexOutput {
+			t.Errorf("%d: got:%s want:%s", i, test.hexOutput, got)
+		}
+	}
+}
+
+var readHeaderTests = []struct {
+	hexInput        string
+	structuralError bool
+	unexpectedEOF   bool
+	tag             int
+	length          int64
+	hexOutput       string
+}{
+	{"", false, false, 0, 0, ""},
+	{"7f", true, false, 0, 0, ""},
+
+	// Old format headers
+	{"80", false, true, 0, 0, ""},
+	{"8001", false, true, 0, 1, ""},
+	{"800102", false, false, 0, 1, "02"},
+	{"81000102", false, false, 0, 1, "02"},
+	{"820000000102", false, false, 0, 1, "02"},
+	{"860000000102", false, false, 1, 1, "02"},
+	{"83010203", false, false, 0, -1, "010203"},
+
+	// New format headers
+	{"c0", false, true, 0, 0, ""},
+	{"c000", false, false, 0, 0, ""},
+	{"c00102", false, false, 0, 1, "02"},
+	{"c0020203", false, false, 0, 2, "0203"},
+	{"c00202", false, true, 0, 2, ""},
+	{"c3020203", false, false, 3, 2, "0203"},
+}
+
+func TestReadHeader(t *testing.T) {
+	for i, test := range readHeaderTests {
+		tag, length, contents, err := readHeader(readerFromHex(test.hexInput))
+		if test.structuralError {
+			if _, ok := err.(errors.StructuralError); ok {
+				continue
+			}
+			t.Errorf("%d: expected StructuralError, got:%s", i, err)
+			continue
+		}
+		if err != nil {
+			if len(test.hexInput) == 0 && err == io.EOF {
+				continue
+			}
+			if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+				t.Errorf("%d: unexpected error from readHeader: %s", i, err)
+			}
+			continue
+		}
+		if int(tag) != test.tag || length != test.length {
+			t.Errorf("%d: got:(%d,%d) want:(%d,%d)", i, int(tag), length, test.tag, test.length)
+			continue
+		}
+
+		body, err := ioutil.ReadAll(contents)
+		if err != nil {
+			if !test.unexpectedEOF || err != io.ErrUnexpectedEOF {
+				t.Errorf("%d: unexpected error from contents: %s", i, err)
+			}
+			continue
+		}
+		if test.unexpectedEOF {
+			t.Errorf("%d: expected ErrUnexpectedEOF from contents but got no error", i)
+			continue
+		}
+		got := fmt.Sprintf("%x", body)
+		if got != test.hexOutput {
+			t.Errorf("%d: got:%s want:%s", i, got, test.hexOutput)
+		}
+	}
+}
+
+func TestSerializeHeader(t *testing.T) {
+	tag := packetTypePublicKey
+	lengths := []int{0, 1, 2, 64, 192, 193, 8000, 8384, 8385, 10000}
+
+	for _, length := range lengths {
+		buf := bytes.NewBuffer(nil)
+		serializeHeader(buf, tag, length)
+		tag2, length2, _, err := readHeader(buf)
+		if err != nil {
+			t.Errorf("length %d, err: %s", length, err)
+		}
+		if tag2 != tag {
+			t.Errorf("length %d, tag incorrect (got %d, want %d)", length, tag2, tag)
+		}
+		if int(length2) != length {
+			t.Errorf("length %d, length incorrect (got %d)", length, length2)
+		}
+	}
+}
+
+func TestPartialLengths(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	w := new(partialLengthWriter)
+	w.w = noOpCloser{buf}
+
+	const maxChunkSize = 64
+
+	var b [maxChunkSize]byte
+	var n uint8
+	for l := 1; l <= maxChunkSize; l++ {
+		for i := 0; i < l; i++ {
+			b[i] = n
+			n++
+		}
+		m, err := w.Write(b[:l])
+		if m != l {
+			t.Errorf("short write got: %d want: %d", m, l)
+		}
+		if err != nil {
+			t.Errorf("error from write: %s", err)
+		}
+	}
+	w.Close()
+
+	want := (maxChunkSize * (maxChunkSize + 1)) / 2
+	copyBuf := bytes.NewBuffer(nil)
+	r := &partialLengthReader{buf, 0, true}
+	m, err := io.Copy(copyBuf, r)
+	if m != int64(want) {
+		t.Errorf("short copy got: %d want: %d", m, want)
+	}
+	if err != nil {
+		t.Errorf("error from copy: %s", err)
+	}
+
+	copyBytes := copyBuf.Bytes()
+	for i := 0; i < want; i++ {
+		if copyBytes[i] != uint8(i) {
+			t.Errorf("bad pattern in copy at %d", i)
+			break
+		}
+	}
+}
diff --git a/openpgp/packet/private_key.go b/openpgp/packet/private_key.go
new file mode 100644
index 0000000..f76c9c5
--- /dev/null
+++ b/openpgp/packet/private_key.go
@@ -0,0 +1,310 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/elgamal"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+	"crypto/cipher"
+	"crypto/dsa"
+	"crypto/rsa"
+	"crypto/sha1"
+	"io"
+	"io/ioutil"
+	"math/big"
+	"strconv"
+	"time"
+)
+
+// PrivateKey represents a possibly encrypted private key. See RFC 4880,
+// section 5.5.3.
+type PrivateKey struct {
+	PublicKey
+	Encrypted     bool // if true then the private key is unavailable until Decrypt has been called.
+	encryptedData []byte
+	cipher        CipherFunction
+	s2k           func(out, in []byte)
+	PrivateKey    interface{} // An *rsa.PrivateKey or *dsa.PrivateKey.
+	sha1Checksum  bool
+	iv            []byte
+}
+
+func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey {
+	pk := new(PrivateKey)
+	pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey)
+	pk.PrivateKey = priv
+	return pk
+}
+
+func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey {
+	pk := new(PrivateKey)
+	pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey)
+	pk.PrivateKey = priv
+	return pk
+}
+
+func (pk *PrivateKey) parse(r io.Reader) (err error) {
+	err = (&pk.PublicKey).parse(r)
+	if err != nil {
+		return
+	}
+	var buf [1]byte
+	_, err = readFull(r, buf[:])
+	if err != nil {
+		return
+	}
+
+	s2kType := buf[0]
+
+	switch s2kType {
+	case 0:
+		pk.s2k = nil
+		pk.Encrypted = false
+	case 254, 255:
+		_, err = readFull(r, buf[:])
+		if err != nil {
+			return
+		}
+		pk.cipher = CipherFunction(buf[0])
+		pk.Encrypted = true
+		pk.s2k, err = s2k.Parse(r)
+		if err != nil {
+			return
+		}
+		if s2kType == 254 {
+			pk.sha1Checksum = true
+		}
+	default:
+		return errors.UnsupportedError("deprecated s2k function in private key")
+	}
+
+	if pk.Encrypted {
+		blockSize := pk.cipher.blockSize()
+		if blockSize == 0 {
+			return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher)))
+		}
+		pk.iv = make([]byte, blockSize)
+		_, err = readFull(r, pk.iv)
+		if err != nil {
+			return
+		}
+	}
+
+	pk.encryptedData, err = ioutil.ReadAll(r)
+	if err != nil {
+		return
+	}
+
+	if !pk.Encrypted {
+		return pk.parsePrivateKey(pk.encryptedData)
+	}
+
+	return
+}
+
+func mod64kHash(d []byte) uint16 {
+	var h uint16
+	for _, b := range d {
+		h += uint16(b)
+	}
+	return h
+}
+
+func (pk *PrivateKey) Serialize(w io.Writer) (err error) {
+	// TODO(agl): support encrypted private keys
+	buf := bytes.NewBuffer(nil)
+	err = pk.PublicKey.serializeWithoutHeaders(buf)
+	if err != nil {
+		return
+	}
+	buf.WriteByte(0 /* no encryption */ )
+
+	privateKeyBuf := bytes.NewBuffer(nil)
+
+	switch priv := pk.PrivateKey.(type) {
+	case *rsa.PrivateKey:
+		err = serializeRSAPrivateKey(privateKeyBuf, priv)
+	case *dsa.PrivateKey:
+		err = serializeDSAPrivateKey(privateKeyBuf, priv)
+	default:
+		err = errors.InvalidArgumentError("unknown private key type")
+	}
+	if err != nil {
+		return
+	}
+
+	ptype := packetTypePrivateKey
+	contents := buf.Bytes()
+	privateKeyBytes := privateKeyBuf.Bytes()
+	if pk.IsSubkey {
+		ptype = packetTypePrivateSubkey
+	}
+	err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2)
+	if err != nil {
+		return
+	}
+	_, err = w.Write(contents)
+	if err != nil {
+		return
+	}
+	_, err = w.Write(privateKeyBytes)
+	if err != nil {
+		return
+	}
+
+	checksum := mod64kHash(privateKeyBytes)
+	var checksumBytes [2]byte
+	checksumBytes[0] = byte(checksum >> 8)
+	checksumBytes[1] = byte(checksum)
+	_, err = w.Write(checksumBytes[:])
+
+	return
+}
+
+func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error {
+	err := writeBig(w, priv.D)
+	if err != nil {
+		return err
+	}
+	err = writeBig(w, priv.Primes[1])
+	if err != nil {
+		return err
+	}
+	err = writeBig(w, priv.Primes[0])
+	if err != nil {
+		return err
+	}
+	return writeBig(w, priv.Precomputed.Qinv)
+}
+
+func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error {
+	return writeBig(w, priv.X)
+}
+
+// Decrypt decrypts an encrypted private key using a passphrase.
+func (pk *PrivateKey) Decrypt(passphrase []byte) error {
+	if !pk.Encrypted {
+		return nil
+	}
+
+	key := make([]byte, pk.cipher.KeySize())
+	pk.s2k(key, passphrase)
+	block := pk.cipher.new(key)
+	cfb := cipher.NewCFBDecrypter(block, pk.iv)
+
+	data := pk.encryptedData
+	cfb.XORKeyStream(data, data)
+
+	if pk.sha1Checksum {
+		if len(data) < sha1.Size {
+			return errors.StructuralError("truncated private key data")
+		}
+		h := sha1.New()
+		h.Write(data[:len(data)-sha1.Size])
+		sum := h.Sum(nil)
+		if !bytes.Equal(sum, data[len(data)-sha1.Size:]) {
+			return errors.StructuralError("private key checksum failure")
+		}
+		data = data[:len(data)-sha1.Size]
+	} else {
+		if len(data) < 2 {
+			return errors.StructuralError("truncated private key data")
+		}
+		var sum uint16
+		for i := 0; i < len(data)-2; i++ {
+			sum += uint16(data[i])
+		}
+		if data[len(data)-2] != uint8(sum>>8) ||
+			data[len(data)-1] != uint8(sum) {
+			return errors.StructuralError("private key checksum failure")
+		}
+		data = data[:len(data)-2]
+	}
+
+	return pk.parsePrivateKey(data)
+}
+
+func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) {
+	switch pk.PublicKey.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly:
+		return pk.parseRSAPrivateKey(data)
+	case PubKeyAlgoDSA:
+		return pk.parseDSAPrivateKey(data)
+	case PubKeyAlgoElGamal:
+		return pk.parseElGamalPrivateKey(data)
+	}
+	panic("impossible")
+}
+
+func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) {
+	rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey)
+	rsaPriv := new(rsa.PrivateKey)
+	rsaPriv.PublicKey = *rsaPub
+
+	buf := bytes.NewBuffer(data)
+	d, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+	p, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+	q, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+
+	rsaPriv.D = new(big.Int).SetBytes(d)
+	rsaPriv.Primes = make([]*big.Int, 2)
+	rsaPriv.Primes[0] = new(big.Int).SetBytes(p)
+	rsaPriv.Primes[1] = new(big.Int).SetBytes(q)
+	rsaPriv.Precompute()
+	pk.PrivateKey = rsaPriv
+	pk.Encrypted = false
+	pk.encryptedData = nil
+
+	return nil
+}
+
+func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) {
+	dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey)
+	dsaPriv := new(dsa.PrivateKey)
+	dsaPriv.PublicKey = *dsaPub
+
+	buf := bytes.NewBuffer(data)
+	x, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+
+	dsaPriv.X = new(big.Int).SetBytes(x)
+	pk.PrivateKey = dsaPriv
+	pk.Encrypted = false
+	pk.encryptedData = nil
+
+	return nil
+}
+
+func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) {
+	pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey)
+	priv := new(elgamal.PrivateKey)
+	priv.PublicKey = *pub
+
+	buf := bytes.NewBuffer(data)
+	x, _, err := readMPI(buf)
+	if err != nil {
+		return
+	}
+
+	priv.X = new(big.Int).SetBytes(x)
+	pk.PrivateKey = priv
+	pk.Encrypted = false
+	pk.encryptedData = nil
+
+	return nil
+}
diff --git a/openpgp/packet/private_key_test.go b/openpgp/packet/private_key_test.go
new file mode 100644
index 0000000..35d8951
--- /dev/null
+++ b/openpgp/packet/private_key_test.go
@@ -0,0 +1,58 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"testing"
+	"time"
+)
+
+var privateKeyTests = []struct {
+	privateKeyHex string
+	creationTime  time.Time
+}{
+	{
+		privKeyRSAHex,
+		time.Unix(0x4cc349a8, 0),
+	},
+	{
+		privKeyElGamalHex,
+		time.Unix(0x4df9ee1a, 0),
+	},
+}
+
+func TestPrivateKeyRead(t *testing.T) {
+	for i, test := range privateKeyTests {
+		packet, err := Read(readerFromHex(test.privateKeyHex))
+		if err != nil {
+			t.Errorf("#%d: failed to parse: %s", i, err)
+			continue
+		}
+
+		privKey := packet.(*PrivateKey)
+
+		if !privKey.Encrypted {
+			t.Errorf("#%d: private key isn't encrypted", i)
+			continue
+		}
+
+		err = privKey.Decrypt([]byte("testing"))
+		if err != nil {
+			t.Errorf("#%d: failed to decrypt: %s", i, err)
+			continue
+		}
+
+		if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted {
+			t.Errorf("#%d: bad result, got: %#v", i, privKey)
+		}
+	}
+}
+
+// Generated with `gpg --export-secret-keys "Test Key 2"`
+const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec"
+
+// Generated by `gpg --export-secret-keys` followed by a manual extraction of
+// the ElGamal subkey from the packets.
+const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc"
diff --git a/openpgp/packet/public_key.go b/openpgp/packet/public_key.go
new file mode 100644
index 0000000..a757090
--- /dev/null
+++ b/openpgp/packet/public_key.go
@@ -0,0 +1,414 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/elgamal"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto/dsa"
+	"crypto/rsa"
+	"crypto/sha1"
+	"encoding/binary"
+	"fmt"
+	"hash"
+	"io"
+	"math/big"
+	"strconv"
+	"time"
+)
+
+// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2.
+type PublicKey struct {
+	CreationTime time.Time
+	PubKeyAlgo   PublicKeyAlgorithm
+	PublicKey    interface{} // Either a *rsa.PublicKey or *dsa.PublicKey
+	Fingerprint  [20]byte
+	KeyId        uint64
+	IsSubkey     bool
+
+	n, e, p, q, g, y parsedMPI
+}
+
+func fromBig(n *big.Int) parsedMPI {
+	return parsedMPI{
+		bytes:     n.Bytes(),
+		bitLength: uint16(n.BitLen()),
+	}
+}
+
+// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
+func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey {
+	pk := &PublicKey{
+		CreationTime: creationTime,
+		PubKeyAlgo:   PubKeyAlgoRSA,
+		PublicKey:    pub,
+		n:            fromBig(pub.N),
+		e:            fromBig(big.NewInt(int64(pub.E))),
+	}
+
+	pk.setFingerPrintAndKeyId()
+	return pk
+}
+
+// NewDSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey.
+func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey {
+	pk := &PublicKey{
+		CreationTime: creationTime,
+		PubKeyAlgo:   PubKeyAlgoDSA,
+		PublicKey:    pub,
+		p:            fromBig(pub.P),
+		q:            fromBig(pub.Q),
+		g:            fromBig(pub.G),
+		y:            fromBig(pub.Y),
+	}
+
+	pk.setFingerPrintAndKeyId()
+	return pk
+}
+
+func (pk *PublicKey) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.5.2
+	var buf [6]byte
+	_, err = readFull(r, buf[:])
+	if err != nil {
+		return
+	}
+	if buf[0] != 4 {
+		return errors.UnsupportedError("public key version")
+	}
+	pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0)
+	pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5])
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		err = pk.parseRSA(r)
+	case PubKeyAlgoDSA:
+		err = pk.parseDSA(r)
+	case PubKeyAlgoElGamal:
+		err = pk.parseElGamal(r)
+	default:
+		err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo)))
+	}
+	if err != nil {
+		return
+	}
+
+	pk.setFingerPrintAndKeyId()
+	return
+}
+
+func (pk *PublicKey) setFingerPrintAndKeyId() {
+	// RFC 4880, section 12.2
+	fingerPrint := sha1.New()
+	pk.SerializeSignaturePrefix(fingerPrint)
+	pk.serializeWithoutHeaders(fingerPrint)
+	copy(pk.Fingerprint[:], fingerPrint.Sum(nil))
+	pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20])
+}
+
+// parseRSA parses RSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseRSA(r io.Reader) (err error) {
+	pk.n.bytes, pk.n.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.e.bytes, pk.e.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+
+	if len(pk.e.bytes) > 3 {
+		err = errors.UnsupportedError("large public exponent")
+		return
+	}
+	rsa := &rsa.PublicKey{
+		N: new(big.Int).SetBytes(pk.n.bytes),
+		E: 0,
+	}
+	for i := 0; i < len(pk.e.bytes); i++ {
+		rsa.E <<= 8
+		rsa.E |= int(pk.e.bytes[i])
+	}
+	pk.PublicKey = rsa
+	return
+}
+
+// parseDSA parses DSA public key material from the given Reader. See RFC 4880,
+// section 5.5.2.
+func (pk *PublicKey) parseDSA(r io.Reader) (err error) {
+	pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.q.bytes, pk.q.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+
+	dsa := new(dsa.PublicKey)
+	dsa.P = new(big.Int).SetBytes(pk.p.bytes)
+	dsa.Q = new(big.Int).SetBytes(pk.q.bytes)
+	dsa.G = new(big.Int).SetBytes(pk.g.bytes)
+	dsa.Y = new(big.Int).SetBytes(pk.y.bytes)
+	pk.PublicKey = dsa
+	return
+}
+
+// parseElGamal parses ElGamal public key material from the given Reader. See
+// RFC 4880, section 5.5.2.
+func (pk *PublicKey) parseElGamal(r io.Reader) (err error) {
+	pk.p.bytes, pk.p.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.g.bytes, pk.g.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+	pk.y.bytes, pk.y.bitLength, err = readMPI(r)
+	if err != nil {
+		return
+	}
+
+	elgamal := new(elgamal.PublicKey)
+	elgamal.P = new(big.Int).SetBytes(pk.p.bytes)
+	elgamal.G = new(big.Int).SetBytes(pk.g.bytes)
+	elgamal.Y = new(big.Int).SetBytes(pk.y.bytes)
+	pk.PublicKey = elgamal
+	return
+}
+
+// SerializeSignaturePrefix writes the prefix for this public key to the given Writer.
+// The prefix is used when calculating a signature over this public key. See
+// RFC 4880, section 5.2.4.
+func (pk *PublicKey) SerializeSignaturePrefix(h hash.Hash) {
+	var pLength uint16
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		pLength += 2 + uint16(len(pk.n.bytes))
+		pLength += 2 + uint16(len(pk.e.bytes))
+	case PubKeyAlgoDSA:
+		pLength += 2 + uint16(len(pk.p.bytes))
+		pLength += 2 + uint16(len(pk.q.bytes))
+		pLength += 2 + uint16(len(pk.g.bytes))
+		pLength += 2 + uint16(len(pk.y.bytes))
+	case PubKeyAlgoElGamal:
+		pLength += 2 + uint16(len(pk.p.bytes))
+		pLength += 2 + uint16(len(pk.g.bytes))
+		pLength += 2 + uint16(len(pk.y.bytes))
+	default:
+		panic("unknown public key algorithm")
+	}
+	pLength += 6
+	h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)})
+	return
+}
+
+func (pk *PublicKey) Serialize(w io.Writer) (err error) {
+	length := 6 // 6 byte header
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		length += 2 + len(pk.n.bytes)
+		length += 2 + len(pk.e.bytes)
+	case PubKeyAlgoDSA:
+		length += 2 + len(pk.p.bytes)
+		length += 2 + len(pk.q.bytes)
+		length += 2 + len(pk.g.bytes)
+		length += 2 + len(pk.y.bytes)
+	case PubKeyAlgoElGamal:
+		length += 2 + len(pk.p.bytes)
+		length += 2 + len(pk.g.bytes)
+		length += 2 + len(pk.y.bytes)
+	default:
+		panic("unknown public key algorithm")
+	}
+
+	packetType := packetTypePublicKey
+	if pk.IsSubkey {
+		packetType = packetTypePublicSubkey
+	}
+	err = serializeHeader(w, packetType, length)
+	if err != nil {
+		return
+	}
+	return pk.serializeWithoutHeaders(w)
+}
+
+// serializeWithoutHeaders marshals the PublicKey to w in the form of an
+// OpenPGP public key packet, not including the packet header.
+func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) {
+	var buf [6]byte
+	buf[0] = 4
+	t := uint32(pk.CreationTime.Unix())
+	buf[1] = byte(t >> 24)
+	buf[2] = byte(t >> 16)
+	buf[3] = byte(t >> 8)
+	buf[4] = byte(t)
+	buf[5] = byte(pk.PubKeyAlgo)
+
+	_, err = w.Write(buf[:])
+	if err != nil {
+		return
+	}
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly:
+		return writeMPIs(w, pk.n, pk.e)
+	case PubKeyAlgoDSA:
+		return writeMPIs(w, pk.p, pk.q, pk.g, pk.y)
+	case PubKeyAlgoElGamal:
+		return writeMPIs(w, pk.p, pk.g, pk.y)
+	}
+	return errors.InvalidArgumentError("bad public-key algorithm")
+}
+
+// CanSign returns true iff this public key can generate signatures
+func (pk *PublicKey) CanSign() bool {
+	return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal
+}
+
+// VerifySignature returns nil iff sig is a valid signature, made by this
+// public key, of the data hashed into signed. signed is mutated by this call.
+func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) {
+	if !pk.CanSign() {
+		return errors.InvalidArgumentError("public key cannot generate signatures")
+	}
+
+	signed.Write(sig.HashSuffix)
+	hashBytes := signed.Sum(nil)
+
+	if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] {
+		return errors.SignatureError("hash tag doesn't match")
+	}
+
+	if pk.PubKeyAlgo != sig.PubKeyAlgo {
+		return errors.InvalidArgumentError("public key and signature use different algorithms")
+	}
+
+	switch pk.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey)
+		err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes)
+		if err != nil {
+			return errors.SignatureError("RSA verification failure")
+		}
+		return nil
+	case PubKeyAlgoDSA:
+		dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey)
+		// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+		subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8
+		if len(hashBytes) > subgroupSize {
+			hashBytes = hashBytes[:subgroupSize]
+		}
+		if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) {
+			return errors.SignatureError("DSA verification failure")
+		}
+		return nil
+	default:
+		panic("shouldn't happen")
+	}
+	panic("unreachable")
+}
+
+// keySignatureHash returns a Hash of the message that needs to be signed for
+// pk to assert a subkey relationship to signed.
+func keySignatureHash(pk, signed *PublicKey, sig *Signature) (h hash.Hash, err error) {
+	h = sig.Hash.New()
+	if h == nil {
+		return nil, errors.UnsupportedError("hash function")
+	}
+
+	// RFC 4880, section 5.2.4
+	pk.SerializeSignaturePrefix(h)
+	pk.serializeWithoutHeaders(h)
+	signed.SerializeSignaturePrefix(h)
+	signed.serializeWithoutHeaders(h)
+	return
+}
+
+// VerifyKeySignature returns nil iff sig is a valid signature, made by this
+// public key, of signed.
+func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err error) {
+	h, err := keySignatureHash(pk, signed, sig)
+	if err != nil {
+		return err
+	}
+	return pk.VerifySignature(h, sig)
+}
+
+// userIdSignatureHash returns a Hash of the message that needs to be signed
+// to assert that pk is a valid key for id.
+func userIdSignatureHash(id string, pk *PublicKey, sig *Signature) (h hash.Hash, err error) {
+	h = sig.Hash.New()
+	if h == nil {
+		return nil, errors.UnsupportedError("hash function")
+	}
+
+	// RFC 4880, section 5.2.4
+	pk.SerializeSignaturePrefix(h)
+	pk.serializeWithoutHeaders(h)
+
+	var buf [5]byte
+	buf[0] = 0xb4
+	buf[1] = byte(len(id) >> 24)
+	buf[2] = byte(len(id) >> 16)
+	buf[3] = byte(len(id) >> 8)
+	buf[4] = byte(len(id))
+	h.Write(buf[:])
+	h.Write([]byte(id))
+
+	return
+}
+
+// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this
+// public key, of id.
+func (pk *PublicKey) VerifyUserIdSignature(id string, sig *Signature) (err error) {
+	h, err := userIdSignatureHash(id, pk, sig)
+	if err != nil {
+		return err
+	}
+	return pk.VerifySignature(h, sig)
+}
+
+// KeyIdString returns the public key's fingerprint in capital hex
+// (e.g. "6C7EE1B8621CC013").
+func (pk *PublicKey) KeyIdString() string {
+	return fmt.Sprintf("%X", pk.Fingerprint[12:20])
+}
+
+// KeyIdShortString returns the short form of public key's fingerprint
+// in capital hex, as shown by gpg --list-keys (e.g. "621CC013").
+func (pk *PublicKey) KeyIdShortString() string {
+	return fmt.Sprintf("%X", pk.Fingerprint[16:20])
+}
+
+// A parsedMPI is used to store the contents of a big integer, along with the
+// bit length that was specified in the original input. This allows the MPI to
+// be reserialized exactly.
+type parsedMPI struct {
+	bytes     []byte
+	bitLength uint16
+}
+
+// writeMPIs is a utility function for serializing several big integers to the
+// given Writer.
+func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) {
+	for _, mpi := range mpis {
+		err = writeMPI(w, mpi.bitLength, mpi.bytes)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
diff --git a/openpgp/packet/public_key_test.go b/openpgp/packet/public_key_test.go
new file mode 100644
index 0000000..72f459f
--- /dev/null
+++ b/openpgp/packet/public_key_test.go
@@ -0,0 +1,99 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"encoding/hex"
+	"testing"
+	"time"
+)
+
+var pubKeyTests = []struct {
+	hexData        string
+	hexFingerprint string
+	creationTime   time.Time
+	pubKeyAlgo     PublicKeyAlgorithm
+	keyId          uint64
+	keyIdString    string
+	keyIdShort     string
+}{
+	{rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"},
+	{dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"},
+}
+
+func TestPublicKeyRead(t *testing.T) {
+	for i, test := range pubKeyTests {
+		packet, err := Read(readerFromHex(test.hexData))
+		if err != nil {
+			t.Errorf("#%d: Read error: %s", i, err)
+			continue
+		}
+		pk, ok := packet.(*PublicKey)
+		if !ok {
+			t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+			continue
+		}
+		if pk.PubKeyAlgo != test.pubKeyAlgo {
+			t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo)
+		}
+		if !pk.CreationTime.Equal(test.creationTime) {
+			t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime)
+		}
+		expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint)
+		if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) {
+			t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint)
+		}
+		if pk.KeyId != test.keyId {
+			t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId)
+		}
+		if g, e := pk.KeyIdString(), test.keyIdString; g != e {
+			t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e)
+		}
+		if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e {
+			t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e)
+		}
+	}
+}
+
+func TestPublicKeySerialize(t *testing.T) {
+	for i, test := range pubKeyTests {
+		packet, err := Read(readerFromHex(test.hexData))
+		if err != nil {
+			t.Errorf("#%d: Read error: %s", i, err)
+			continue
+		}
+		pk, ok := packet.(*PublicKey)
+		if !ok {
+			t.Errorf("#%d: failed to parse, got: %#v", i, packet)
+			continue
+		}
+		serializeBuf := bytes.NewBuffer(nil)
+		err = pk.Serialize(serializeBuf)
+		if err != nil {
+			t.Errorf("#%d: failed to serialize: %s", i, err)
+			continue
+		}
+
+		packet, err = Read(serializeBuf)
+		if err != nil {
+			t.Errorf("#%d: Read error (from serialized data): %s", i, err)
+			continue
+		}
+		pk, ok = packet.(*PublicKey)
+		if !ok {
+			t.Errorf("#%d: failed to parse serialized data, got: %#v", i, packet)
+			continue
+		}
+	}
+}
+
+const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb"
+
+const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001"
+
+const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed"
+
+const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0"
diff --git a/openpgp/packet/reader.go b/openpgp/packet/reader.go
new file mode 100644
index 0000000..3325d63
--- /dev/null
+++ b/openpgp/packet/reader.go
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"io"
+)
+
+// Reader reads packets from an io.Reader and allows packets to be 'unread' so
+// that they result from the next call to Next.
+type Reader struct {
+	q       []Packet
+	readers []io.Reader
+}
+
+// Next returns the most recently unread Packet, or reads another packet from
+// the top-most io.Reader. Unknown packet types are skipped.
+func (r *Reader) Next() (p Packet, err error) {
+	if len(r.q) > 0 {
+		p = r.q[len(r.q)-1]
+		r.q = r.q[:len(r.q)-1]
+		return
+	}
+
+	for len(r.readers) > 0 {
+		p, err = Read(r.readers[len(r.readers)-1])
+		if err == nil {
+			return
+		}
+		if err == io.EOF {
+			r.readers = r.readers[:len(r.readers)-1]
+			continue
+		}
+		if _, ok := err.(errors.UnknownPacketTypeError); !ok {
+			return nil, err
+		}
+	}
+
+	return nil, io.EOF
+}
+
+// Push causes the Reader to start reading from a new io.Reader. When an EOF
+// error is seen from the new io.Reader, it is popped and the Reader continues
+// to read from the next most recent io.Reader.
+func (r *Reader) Push(reader io.Reader) {
+	r.readers = append(r.readers, reader)
+}
+
+// Unread causes the given Packet to be returned from the next call to Next.
+func (r *Reader) Unread(p Packet) {
+	r.q = append(r.q, p)
+}
+
+func NewReader(r io.Reader) *Reader {
+	return &Reader{
+		q:       nil,
+		readers: []io.Reader{r},
+	}
+}
diff --git a/openpgp/packet/signature.go b/openpgp/packet/signature.go
new file mode 100644
index 0000000..7f2fdab
--- /dev/null
+++ b/openpgp/packet/signature.go
@@ -0,0 +1,611 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+	"crypto"
+	"crypto/dsa"
+	"crypto/rsa"
+	"encoding/binary"
+	"hash"
+	"io"
+	"strconv"
+	"time"
+)
+
+// Signature represents a signature. See RFC 4880, section 5.2.
+type Signature struct {
+	SigType    SignatureType
+	PubKeyAlgo PublicKeyAlgorithm
+	Hash       crypto.Hash
+
+	// HashSuffix is extra data that is hashed in after the signed data.
+	HashSuffix []byte
+	// HashTag contains the first two bytes of the hash for fast rejection
+	// of bad signed data.
+	HashTag      [2]byte
+	CreationTime time.Time
+
+	RSASignature     parsedMPI
+	DSASigR, DSASigS parsedMPI
+
+	// rawSubpackets contains the unparsed subpackets, in order.
+	rawSubpackets []outputSubpacket
+
+	// The following are optional so are nil when not included in the
+	// signature.
+
+	SigLifetimeSecs, KeyLifetimeSecs                        *uint32
+	PreferredSymmetric, PreferredHash, PreferredCompression []uint8
+	IssuerKeyId                                             *uint64
+	IsPrimaryId                                             *bool
+
+	// FlagsValid is set if any flags were given. See RFC 4880, section
+	// 5.2.3.21 for details.
+	FlagsValid                                                           bool
+	FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool
+
+	outSubpackets []outputSubpacket
+}
+
+func (sig *Signature) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.2.3
+	var buf [5]byte
+	_, err = readFull(r, buf[:1])
+	if err != nil {
+		return
+	}
+	if buf[0] != 4 {
+		err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0])))
+		return
+	}
+
+	_, err = readFull(r, buf[:5])
+	if err != nil {
+		return
+	}
+	sig.SigType = SignatureType(buf[0])
+	sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1])
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA:
+	default:
+		err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo)))
+		return
+	}
+
+	var ok bool
+	sig.Hash, ok = s2k.HashIdToHash(buf[2])
+	if !ok {
+		return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
+	}
+
+	hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4])
+	l := 6 + hashedSubpacketsLength
+	sig.HashSuffix = make([]byte, l+6)
+	sig.HashSuffix[0] = 4
+	copy(sig.HashSuffix[1:], buf[:5])
+	hashedSubpackets := sig.HashSuffix[6:l]
+	_, err = readFull(r, hashedSubpackets)
+	if err != nil {
+		return
+	}
+	// See RFC 4880, section 5.2.4
+	trailer := sig.HashSuffix[l:]
+	trailer[0] = 4
+	trailer[1] = 0xff
+	trailer[2] = uint8(l >> 24)
+	trailer[3] = uint8(l >> 16)
+	trailer[4] = uint8(l >> 8)
+	trailer[5] = uint8(l)
+
+	err = parseSignatureSubpackets(sig, hashedSubpackets, true)
+	if err != nil {
+		return
+	}
+
+	_, err = readFull(r, buf[:2])
+	if err != nil {
+		return
+	}
+	unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1])
+	unhashedSubpackets := make([]byte, unhashedSubpacketsLength)
+	_, err = readFull(r, unhashedSubpackets)
+	if err != nil {
+		return
+	}
+	err = parseSignatureSubpackets(sig, unhashedSubpackets, false)
+	if err != nil {
+		return
+	}
+
+	_, err = readFull(r, sig.HashTag[:2])
+	if err != nil {
+		return
+	}
+
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r)
+	case PubKeyAlgoDSA:
+		sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r)
+		if err == nil {
+			sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r)
+		}
+	default:
+		panic("unreachable")
+	}
+	return
+}
+
+// parseSignatureSubpackets parses subpackets of the main signature packet. See
+// RFC 4880, section 5.2.3.1.
+func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) {
+	for len(subpackets) > 0 {
+		subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed)
+		if err != nil {
+			return
+		}
+	}
+
+	if sig.CreationTime.IsZero() {
+		err = errors.StructuralError("no creation time in signature")
+	}
+
+	return
+}
+
+type signatureSubpacketType uint8
+
+const (
+	creationTimeSubpacket        signatureSubpacketType = 2
+	signatureExpirationSubpacket signatureSubpacketType = 3
+	keyExpirationSubpacket       signatureSubpacketType = 9
+	prefSymmetricAlgosSubpacket  signatureSubpacketType = 11
+	issuerSubpacket              signatureSubpacketType = 16
+	prefHashAlgosSubpacket       signatureSubpacketType = 21
+	prefCompressionSubpacket     signatureSubpacketType = 22
+	primaryUserIdSubpacket       signatureSubpacketType = 25
+	keyFlagsSubpacket            signatureSubpacketType = 27
+)
+
+// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
+func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) {
+	// RFC 4880, section 5.2.3.1
+	var (
+		length     uint32
+		packetType signatureSubpacketType
+		isCritical bool
+	)
+	switch {
+	case subpacket[0] < 192:
+		length = uint32(subpacket[0])
+		subpacket = subpacket[1:]
+	case subpacket[0] < 255:
+		if len(subpacket) < 2 {
+			goto Truncated
+		}
+		length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192
+		subpacket = subpacket[2:]
+	default:
+		if len(subpacket) < 5 {
+			goto Truncated
+		}
+		length = uint32(subpacket[1])<<24 |
+			uint32(subpacket[2])<<16 |
+			uint32(subpacket[3])<<8 |
+			uint32(subpacket[4])
+		subpacket = subpacket[5:]
+	}
+	if length > uint32(len(subpacket)) {
+		goto Truncated
+	}
+	rest = subpacket[length:]
+	subpacket = subpacket[:length]
+	if len(subpacket) == 0 {
+		err = errors.StructuralError("zero length signature subpacket")
+		return
+	}
+	packetType = signatureSubpacketType(subpacket[0] & 0x7f)
+	isCritical = subpacket[0]&0x80 == 0x80
+	subpacket = subpacket[1:]
+	sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
+	switch packetType {
+	case creationTimeSubpacket:
+		if !isHashed {
+			err = errors.StructuralError("signature creation time in non-hashed area")
+			return
+		}
+		if len(subpacket) != 4 {
+			err = errors.StructuralError("signature creation time not four bytes")
+			return
+		}
+		t := binary.BigEndian.Uint32(subpacket)
+		sig.CreationTime = time.Unix(int64(t), 0)
+	case signatureExpirationSubpacket:
+		// Signature expiration time, section 5.2.3.10
+		if !isHashed {
+			return
+		}
+		if len(subpacket) != 4 {
+			err = errors.StructuralError("expiration subpacket with bad length")
+			return
+		}
+		sig.SigLifetimeSecs = new(uint32)
+		*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+	case keyExpirationSubpacket:
+		// Key expiration time, section 5.2.3.6
+		if !isHashed {
+			return
+		}
+		if len(subpacket) != 4 {
+			err = errors.StructuralError("key expiration subpacket with bad length")
+			return
+		}
+		sig.KeyLifetimeSecs = new(uint32)
+		*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
+	case prefSymmetricAlgosSubpacket:
+		// Preferred symmetric algorithms, section 5.2.3.7
+		if !isHashed {
+			return
+		}
+		sig.PreferredSymmetric = make([]byte, len(subpacket))
+		copy(sig.PreferredSymmetric, subpacket)
+	case issuerSubpacket:
+		// Issuer, section 5.2.3.5
+		if len(subpacket) != 8 {
+			err = errors.StructuralError("issuer subpacket with bad length")
+			return
+		}
+		sig.IssuerKeyId = new(uint64)
+		*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
+	case prefHashAlgosSubpacket:
+		// Preferred hash algorithms, section 5.2.3.8
+		if !isHashed {
+			return
+		}
+		sig.PreferredHash = make([]byte, len(subpacket))
+		copy(sig.PreferredHash, subpacket)
+	case prefCompressionSubpacket:
+		// Preferred compression algorithms, section 5.2.3.9
+		if !isHashed {
+			return
+		}
+		sig.PreferredCompression = make([]byte, len(subpacket))
+		copy(sig.PreferredCompression, subpacket)
+	case primaryUserIdSubpacket:
+		// Primary User ID, section 5.2.3.19
+		if !isHashed {
+			return
+		}
+		if len(subpacket) != 1 {
+			err = errors.StructuralError("primary user id subpacket with bad length")
+			return
+		}
+		sig.IsPrimaryId = new(bool)
+		if subpacket[0] > 0 {
+			*sig.IsPrimaryId = true
+		}
+	case keyFlagsSubpacket:
+		// Key flags, section 5.2.3.21
+		if !isHashed {
+			return
+		}
+		if len(subpacket) == 0 {
+			err = errors.StructuralError("empty key flags subpacket")
+			return
+		}
+		sig.FlagsValid = true
+		if subpacket[0]&1 != 0 {
+			sig.FlagCertify = true
+		}
+		if subpacket[0]&2 != 0 {
+			sig.FlagSign = true
+		}
+		if subpacket[0]&4 != 0 {
+			sig.FlagEncryptCommunications = true
+		}
+		if subpacket[0]&8 != 0 {
+			sig.FlagEncryptStorage = true
+		}
+
+	default:
+		if isCritical {
+			err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
+			return
+		}
+	}
+	return
+
+Truncated:
+	err = errors.StructuralError("signature subpacket truncated")
+	return
+}
+
+// subpacketLengthLength returns the length, in bytes, of an encoded length value.
+func subpacketLengthLength(length int) int {
+	if length < 192 {
+		return 1
+	}
+	if length < 16320 {
+		return 2
+	}
+	return 5
+}
+
+// serializeSubpacketLength marshals the given length into to.
+func serializeSubpacketLength(to []byte, length int) int {
+	if length < 192 {
+		to[0] = byte(length)
+		return 1
+	}
+	if length < 16320 {
+		length -= 192
+		to[0] = byte(length >> 8)
+		to[1] = byte(length)
+		return 2
+	}
+	to[0] = 255
+	to[1] = byte(length >> 24)
+	to[2] = byte(length >> 16)
+	to[3] = byte(length >> 8)
+	to[4] = byte(length)
+	return 5
+}
+
+// subpacketsLength returns the serialized length, in bytes, of the given
+// subpackets.
+func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) {
+	for _, subpacket := range subpackets {
+		if subpacket.hashed == hashed {
+			length += subpacketLengthLength(len(subpacket.contents) + 1)
+			length += 1 // type byte
+			length += len(subpacket.contents)
+		}
+	}
+	return
+}
+
+// serializeSubpackets marshals the given subpackets into to.
+func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) {
+	for _, subpacket := range subpackets {
+		if subpacket.hashed == hashed {
+			n := serializeSubpacketLength(to, len(subpacket.contents)+1)
+			to[n] = byte(subpacket.subpacketType)
+			to = to[1+n:]
+			n = copy(to, subpacket.contents)
+			to = to[n:]
+		}
+	}
+	return
+}
+
+// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
+func (sig *Signature) buildHashSuffix() (err error) {
+	hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true)
+
+	var ok bool
+	l := 6 + hashedSubpacketsLen
+	sig.HashSuffix = make([]byte, l+6)
+	sig.HashSuffix[0] = 4
+	sig.HashSuffix[1] = uint8(sig.SigType)
+	sig.HashSuffix[2] = uint8(sig.PubKeyAlgo)
+	sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash)
+	if !ok {
+		sig.HashSuffix = nil
+		return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
+	}
+	sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8)
+	sig.HashSuffix[5] = byte(hashedSubpacketsLen)
+	serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true)
+	trailer := sig.HashSuffix[l:]
+	trailer[0] = 4
+	trailer[1] = 0xff
+	trailer[2] = byte(l >> 24)
+	trailer[3] = byte(l >> 16)
+	trailer[4] = byte(l >> 8)
+	trailer[5] = byte(l)
+	return
+}
+
+func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) {
+	err = sig.buildHashSuffix()
+	if err != nil {
+		return
+	}
+
+	h.Write(sig.HashSuffix)
+	digest = h.Sum(nil)
+	copy(sig.HashTag[:], digest)
+	return
+}
+
+// Sign signs a message with a private key. The hash, h, must contain
+// the hash of the message to be signed and will be mutated by this function.
+// On success, the signature is stored in sig. Call Serialize to write it out.
+func (sig *Signature) Sign(rand io.Reader, h hash.Hash, priv *PrivateKey) (err error) {
+	sig.outSubpackets = sig.buildSubpackets()
+	digest, err := sig.signPrepareHash(h)
+	if err != nil {
+		return
+	}
+
+	switch priv.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		sig.RSASignature.bytes, err = rsa.SignPKCS1v15(rand, priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest)
+		sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes))
+	case PubKeyAlgoDSA:
+		dsaPriv := priv.PrivateKey.(*dsa.PrivateKey)
+
+		// Need to truncate hashBytes to match FIPS 186-3 section 4.6.
+		subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8
+		if len(digest) > subgroupSize {
+			digest = digest[:subgroupSize]
+		}
+		r, s, err := dsa.Sign(rand, dsaPriv, digest)
+		if err == nil {
+			sig.DSASigR.bytes = r.Bytes()
+			sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes))
+			sig.DSASigS.bytes = s.Bytes()
+			sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes))
+		}
+	default:
+		err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo)))
+	}
+
+	return
+}
+
+// SignUserId computes a signature from priv, asserting that pub is a valid
+// key for the identity id.  On success, the signature is stored in sig. Call
+// Serialize to write it out.
+func (sig *Signature) SignUserId(rand io.Reader, id string, pub *PublicKey, priv *PrivateKey) error {
+	h, err := userIdSignatureHash(id, pub, sig)
+	if err != nil {
+		return nil
+	}
+	return sig.Sign(rand, h, priv)
+}
+
+// SignKey computes a signature from priv, asserting that pub is a subkey.  On
+// success, the signature is stored in sig. Call Serialize to write it out.
+func (sig *Signature) SignKey(rand io.Reader, pub *PublicKey, priv *PrivateKey) error {
+	h, err := keySignatureHash(&priv.PublicKey, pub, sig)
+	if err != nil {
+		return err
+	}
+	return sig.Sign(rand, h, priv)
+}
+
+// Serialize marshals sig to w. SignRSA or SignDSA must have been called first.
+func (sig *Signature) Serialize(w io.Writer) (err error) {
+	if len(sig.outSubpackets) == 0 {
+		sig.outSubpackets = sig.rawSubpackets
+	}
+	if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil {
+		return errors.InvalidArgumentError("Signature: need to call SignRSA or SignDSA before Serialize")
+	}
+
+	sigLength := 0
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		sigLength = 2 + len(sig.RSASignature.bytes)
+	case PubKeyAlgoDSA:
+		sigLength = 2 + len(sig.DSASigR.bytes)
+		sigLength += 2 + len(sig.DSASigS.bytes)
+	default:
+		panic("impossible")
+	}
+
+	unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false)
+	length := len(sig.HashSuffix) - 6 /* trailer not included */ +
+		2 /* length of unhashed subpackets */ + unhashedSubpacketsLen +
+		2 /* hash tag */ + sigLength
+	err = serializeHeader(w, packetTypeSignature, length)
+	if err != nil {
+		return
+	}
+
+	_, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6])
+	if err != nil {
+		return
+	}
+
+	unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen)
+	unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8)
+	unhashedSubpackets[1] = byte(unhashedSubpacketsLen)
+	serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false)
+
+	_, err = w.Write(unhashedSubpackets)
+	if err != nil {
+		return
+	}
+	_, err = w.Write(sig.HashTag[:])
+	if err != nil {
+		return
+	}
+
+	switch sig.PubKeyAlgo {
+	case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly:
+		err = writeMPIs(w, sig.RSASignature)
+	case PubKeyAlgoDSA:
+		err = writeMPIs(w, sig.DSASigR, sig.DSASigS)
+	default:
+		panic("impossible")
+	}
+	return
+}
+
+// outputSubpacket represents a subpacket to be marshaled.
+type outputSubpacket struct {
+	hashed        bool // true if this subpacket is in the hashed area.
+	subpacketType signatureSubpacketType
+	isCritical    bool
+	contents      []byte
+}
+
+func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) {
+	creationTime := make([]byte, 4)
+	binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix()))
+	subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime})
+
+	if sig.IssuerKeyId != nil {
+		keyId := make([]byte, 8)
+		binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
+		subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
+	}
+
+	if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 {
+		sigLifetime := make([]byte, 4)
+		binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs)
+		subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime})
+	}
+
+	// Key flags may only appear in self-signatures or certification signatures.
+
+	if sig.FlagsValid {
+		var flags byte
+		if sig.FlagCertify {
+			flags |= 1
+		}
+		if sig.FlagSign {
+			flags |= 2
+		}
+		if sig.FlagEncryptCommunications {
+			flags |= 4
+		}
+		if sig.FlagEncryptStorage {
+			flags |= 8
+		}
+		subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
+	}
+
+	// The following subpackets may only appear in self-signatures
+
+	if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
+		keyLifetime := make([]byte, 4)
+		binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
+		subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime})
+	}
+
+	if sig.IsPrimaryId != nil && *sig.IsPrimaryId {
+		subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}})
+	}
+
+	if len(sig.PreferredSymmetric) > 0 {
+		subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric})
+	}
+
+	if len(sig.PreferredHash) > 0 {
+		subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash})
+	}
+
+	if len(sig.PreferredCompression) > 0 {
+		subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression})
+	}
+
+	return
+}
diff --git a/openpgp/packet/signature_test.go b/openpgp/packet/signature_test.go
new file mode 100644
index 0000000..c1bbde8
--- /dev/null
+++ b/openpgp/packet/signature_test.go
@@ -0,0 +1,42 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"crypto"
+	"encoding/hex"
+	"testing"
+)
+
+func TestSignatureRead(t *testing.T) {
+	packet, err := Read(readerFromHex(signatureDataHex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	sig, ok := packet.(*Signature)
+	if !ok || sig.SigType != SigTypeBinary || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.SHA1 {
+		t.Errorf("failed to parse, got: %#v", packet)
+	}
+}
+
+func TestSignatureReserialize(t *testing.T) {
+	packet, _ := Read(readerFromHex(signatureDataHex))
+	sig := packet.(*Signature)
+	out := new(bytes.Buffer)
+	err := sig.Serialize(out)
+	if err != nil {
+		t.Errorf("error reserializing: %s", err)
+		return
+	}
+
+	expected, _ := hex.DecodeString(signatureDataHex)
+	if !bytes.Equal(expected, out.Bytes()) {
+		t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected))
+	}
+}
+
+const signatureDataHex = "c2c05c04000102000605024cb45112000a0910ab105c91af38fb158f8d07ff5596ea368c5efe015bed6e78348c0f033c931d5f2ce5db54ce7f2a7e4b4ad64db758d65a7a71773edeab7ba2a9e0908e6a94a1175edd86c1d843279f045b021a6971a72702fcbd650efc393c5474d5b59a15f96d2eaad4c4c426797e0dcca2803ef41c6ff234d403eec38f31d610c344c06f2401c262f0993b2e66cad8a81ebc4322c723e0d4ba09fe917e8777658307ad8329adacba821420741009dfe87f007759f0982275d028a392c6ed983a0d846f890b36148c7358bdb8a516007fac760261ecd06076813831a36d0459075d1befa245ae7f7fb103d92ca759e9498fe60ef8078a39a3beda510deea251ea9f0a7f0df6ef42060f20780360686f3e400e"
diff --git a/openpgp/packet/symmetric_key_encrypted.go b/openpgp/packet/symmetric_key_encrypted.go
new file mode 100644
index 0000000..574badd
--- /dev/null
+++ b/openpgp/packet/symmetric_key_encrypted.go
@@ -0,0 +1,161 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+	"crypto/cipher"
+	"io"
+	"strconv"
+)
+
+// This is the largest session key that we'll support. Since no 512-bit cipher
+// has even been seriously used, this is comfortably large.
+const maxSessionKeySizeInBytes = 64
+
+// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
+// 4880, section 5.3.
+type SymmetricKeyEncrypted struct {
+	CipherFunc   CipherFunction
+	Encrypted    bool
+	Key          []byte // Empty unless Encrypted is false.
+	s2k          func(out, in []byte)
+	encryptedKey []byte
+}
+
+const symmetricKeyEncryptedVersion = 4
+
+func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.3.
+	var buf [2]byte
+	_, err = readFull(r, buf[:])
+	if err != nil {
+		return
+	}
+	if buf[0] != symmetricKeyEncryptedVersion {
+		return errors.UnsupportedError("SymmetricKeyEncrypted version")
+	}
+	ske.CipherFunc = CipherFunction(buf[1])
+
+	if ske.CipherFunc.KeySize() == 0 {
+		return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
+	}
+
+	ske.s2k, err = s2k.Parse(r)
+	if err != nil {
+		return
+	}
+
+	encryptedKey := make([]byte, maxSessionKeySizeInBytes)
+	// The session key may follow. We just have to try and read to find
+	// out. If it exists then we limit it to maxSessionKeySizeInBytes.
+	n, err := readFull(r, encryptedKey)
+	if err != nil && err != io.ErrUnexpectedEOF {
+		return
+	}
+	err = nil
+	if n != 0 {
+		if n == maxSessionKeySizeInBytes {
+			return errors.UnsupportedError("oversized encrypted session key")
+		}
+		ske.encryptedKey = encryptedKey[:n]
+	}
+
+	ske.Encrypted = true
+
+	return
+}
+
+// Decrypt attempts to decrypt an encrypted session key. If it returns nil,
+// ske.Key will contain the session key.
+func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error {
+	if !ske.Encrypted {
+		return nil
+	}
+
+	key := make([]byte, ske.CipherFunc.KeySize())
+	ske.s2k(key, passphrase)
+
+	if len(ske.encryptedKey) == 0 {
+		ske.Key = key
+	} else {
+		// the IV is all zeros
+		iv := make([]byte, ske.CipherFunc.blockSize())
+		c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv)
+		c.XORKeyStream(ske.encryptedKey, ske.encryptedKey)
+		ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
+		if ske.CipherFunc.blockSize() == 0 {
+			return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc)))
+		}
+		ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
+		ske.Key = ske.encryptedKey[1:]
+		if len(ske.Key)%ske.CipherFunc.blockSize() != 0 {
+			ske.Key = nil
+			return errors.StructuralError("length of decrypted key not a multiple of block size")
+		}
+	}
+
+	ske.Encrypted = false
+	return nil
+}
+
+// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. The
+// packet contains a random session key, encrypted by a key derived from the
+// given passphrase. The session key is returned and must be passed to
+// SerializeSymmetricallyEncrypted.
+func SerializeSymmetricKeyEncrypted(w io.Writer, rand io.Reader, passphrase []byte, cipherFunc CipherFunction) (key []byte, err error) {
+	keySize := cipherFunc.KeySize()
+	if keySize == 0 {
+		return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
+	}
+
+	s2kBuf := new(bytes.Buffer)
+	keyEncryptingKey := make([]byte, keySize)
+	// s2k.Serialize salts and stretches the passphrase, and writes the
+	// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
+	err = s2k.Serialize(s2kBuf, keyEncryptingKey, rand, passphrase)
+	if err != nil {
+		return
+	}
+	s2kBytes := s2kBuf.Bytes()
+
+	packetLength := 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
+	err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
+	if err != nil {
+		return
+	}
+
+	var buf [2]byte
+	buf[0] = symmetricKeyEncryptedVersion
+	buf[1] = byte(cipherFunc)
+	_, err = w.Write(buf[:])
+	if err != nil {
+		return
+	}
+	_, err = w.Write(s2kBytes)
+	if err != nil {
+		return
+	}
+
+	sessionKey := make([]byte, keySize)
+	_, err = io.ReadFull(rand, sessionKey)
+	if err != nil {
+		return
+	}
+	iv := make([]byte, cipherFunc.blockSize())
+	c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv)
+	encryptedCipherAndKey := make([]byte, keySize+1)
+	c.XORKeyStream(encryptedCipherAndKey, buf[1:])
+	c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey)
+	_, err = w.Write(encryptedCipherAndKey)
+	if err != nil {
+		return
+	}
+
+	key = sessionKey
+	return
+}
diff --git a/openpgp/packet/symmetric_key_encrypted_test.go b/openpgp/packet/symmetric_key_encrypted_test.go
new file mode 100644
index 0000000..87690f0
--- /dev/null
+++ b/openpgp/packet/symmetric_key_encrypted_test.go
@@ -0,0 +1,101 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"crypto/rand"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+func TestSymmetricKeyEncrypted(t *testing.T) {
+	buf := readerFromHex(symmetricallyEncryptedHex)
+	packet, err := Read(buf)
+	if err != nil {
+		t.Errorf("failed to read SymmetricKeyEncrypted: %s", err)
+		return
+	}
+	ske, ok := packet.(*SymmetricKeyEncrypted)
+	if !ok {
+		t.Error("didn't find SymmetricKeyEncrypted packet")
+		return
+	}
+	err = ske.Decrypt([]byte("password"))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	packet, err = Read(buf)
+	if err != nil {
+		t.Errorf("failed to read SymmetricallyEncrypted: %s", err)
+		return
+	}
+	se, ok := packet.(*SymmetricallyEncrypted)
+	if !ok {
+		t.Error("didn't find SymmetricallyEncrypted packet")
+		return
+	}
+	r, err := se.Decrypt(ske.CipherFunc, ske.Key)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	contents, err := ioutil.ReadAll(r)
+	if err != nil && err != io.EOF {
+		t.Error(err)
+		return
+	}
+
+	expectedContents, _ := hex.DecodeString(symmetricallyEncryptedContentsHex)
+	if !bytes.Equal(expectedContents, contents) {
+		t.Errorf("bad contents got:%x want:%x", contents, expectedContents)
+	}
+}
+
+const symmetricallyEncryptedHex = "8c0d04030302371a0b38d884f02060c91cf97c9973b8e58e028e9501708ccfe618fb92afef7fa2d80ddadd93cf"
+const symmetricallyEncryptedContentsHex = "cb1062004d14c4df636f6e74656e74732e0a"
+
+func TestSerializeSymmetricKeyEncrypted(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	passphrase := []byte("testing")
+	cipherFunc := CipherAES128
+
+	key, err := SerializeSymmetricKeyEncrypted(buf, rand.Reader, passphrase, cipherFunc)
+	if err != nil {
+		t.Errorf("failed to serialize: %s", err)
+		return
+	}
+
+	p, err := Read(buf)
+	if err != nil {
+		t.Errorf("failed to reparse: %s", err)
+		return
+	}
+	ske, ok := p.(*SymmetricKeyEncrypted)
+	if !ok {
+		t.Errorf("parsed a different packet type: %#v", p)
+		return
+	}
+
+	if !ske.Encrypted {
+		t.Errorf("SKE not encrypted but should be")
+	}
+	if ske.CipherFunc != cipherFunc {
+		t.Errorf("SKE cipher function is %d (expected %d)", ske.CipherFunc, cipherFunc)
+	}
+	err = ske.Decrypt(passphrase)
+	if err != nil {
+		t.Errorf("failed to decrypt reparsed SKE: %s", err)
+		return
+	}
+	if !bytes.Equal(key, ske.Key) {
+		t.Errorf("keys don't match after Decrpyt: %x (original) vs %x (parsed)", key, ske.Key)
+	}
+}
diff --git a/openpgp/packet/symmetrically_encrypted.go b/openpgp/packet/symmetrically_encrypted.go
new file mode 100644
index 0000000..a420768
--- /dev/null
+++ b/openpgp/packet/symmetrically_encrypted.go
@@ -0,0 +1,289 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto/cipher"
+	"crypto/sha1"
+	"crypto/subtle"
+	"hash"
+	"io"
+	"strconv"
+)
+
+// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
+// encrypted contents will consist of more OpenPGP packets. See RFC 4880,
+// sections 5.7 and 5.13.
+type SymmetricallyEncrypted struct {
+	MDC      bool // true iff this is a type 18 packet and thus has an embedded MAC.
+	contents io.Reader
+	prefix   []byte
+}
+
+const symmetricallyEncryptedVersion = 1
+
+func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
+	if se.MDC {
+		// See RFC 4880, section 5.13.
+		var buf [1]byte
+		_, err := readFull(r, buf[:])
+		if err != nil {
+			return err
+		}
+		if buf[0] != symmetricallyEncryptedVersion {
+			return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
+		}
+	}
+	se.contents = r
+	return nil
+}
+
+// Decrypt returns a ReadCloser, from which the decrypted contents of the
+// packet can be read. An incorrect key can, with high probability, be detected
+// immediately and this will result in a KeyIncorrect error being returned.
+func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
+	keySize := c.KeySize()
+	if keySize == 0 {
+		return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
+	}
+	if len(key) != keySize {
+		return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
+	}
+
+	if se.prefix == nil {
+		se.prefix = make([]byte, c.blockSize()+2)
+		_, err := readFull(se.contents, se.prefix)
+		if err != nil {
+			return nil, err
+		}
+	} else if len(se.prefix) != c.blockSize()+2 {
+		return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
+	}
+
+	ocfbResync := cipher.OCFBResync
+	if se.MDC {
+		// MDC packets use a different form of OCFB mode.
+		ocfbResync = cipher.OCFBNoResync
+	}
+
+	s := cipher.NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
+	if s == nil {
+		return nil, errors.ErrKeyIncorrect
+	}
+
+	plaintext := cipher.StreamReader{S: s, R: se.contents}
+
+	if se.MDC {
+		// MDC packets have an embedded hash that we need to check.
+		h := sha1.New()
+		h.Write(se.prefix)
+		return &seMDCReader{in: plaintext, h: h}, nil
+	}
+
+	// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
+	return seReader{plaintext}, nil
+}
+
+// seReader wraps an io.Reader with a no-op Close method.
+type seReader struct {
+	in io.Reader
+}
+
+func (ser seReader) Read(buf []byte) (int, error) {
+	return ser.in.Read(buf)
+}
+
+func (ser seReader) Close() error {
+	return nil
+}
+
+const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
+
+// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
+// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
+// MDC packet containing a hash of the previous contents which is checked
+// against the running hash. See RFC 4880, section 5.13.
+type seMDCReader struct {
+	in          io.Reader
+	h           hash.Hash
+	trailer     [mdcTrailerSize]byte
+	scratch     [mdcTrailerSize]byte
+	trailerUsed int
+	error       bool
+	eof         bool
+}
+
+func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
+	if ser.error {
+		err = io.ErrUnexpectedEOF
+		return
+	}
+	if ser.eof {
+		err = io.EOF
+		return
+	}
+
+	// If we haven't yet filled the trailer buffer then we must do that
+	// first.
+	for ser.trailerUsed < mdcTrailerSize {
+		n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
+		ser.trailerUsed += n
+		if err == io.EOF {
+			if ser.trailerUsed != mdcTrailerSize {
+				n = 0
+				err = io.ErrUnexpectedEOF
+				ser.error = true
+				return
+			}
+			ser.eof = true
+			n = 0
+			return
+		}
+
+		if err != nil {
+			n = 0
+			return
+		}
+	}
+
+	// If it's a short read then we read into a temporary buffer and shift
+	// the data into the caller's buffer.
+	if len(buf) <= mdcTrailerSize {
+		n, err = readFull(ser.in, ser.scratch[:len(buf)])
+		copy(buf, ser.trailer[:n])
+		ser.h.Write(buf[:n])
+		copy(ser.trailer[:], ser.trailer[n:])
+		copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
+		if n < len(buf) {
+			ser.eof = true
+			err = io.EOF
+		}
+		return
+	}
+
+	n, err = ser.in.Read(buf[mdcTrailerSize:])
+	copy(buf, ser.trailer[:])
+	ser.h.Write(buf[:n])
+	copy(ser.trailer[:], buf[n:])
+
+	if err == io.EOF {
+		ser.eof = true
+	}
+	return
+}
+
+// This is a new-format packet tag byte for a type 19 (MDC) packet.
+const mdcPacketTagByte = byte(0x80) | 0x40 | 19
+
+func (ser *seMDCReader) Close() error {
+	if ser.error {
+		return errors.SignatureError("error during reading")
+	}
+
+	for !ser.eof {
+		// We haven't seen EOF so we need to read to the end
+		var buf [1024]byte
+		_, err := ser.Read(buf[:])
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return errors.SignatureError("error during reading")
+		}
+	}
+
+	if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
+		return errors.SignatureError("MDC packet not found")
+	}
+	ser.h.Write(ser.trailer[:2])
+
+	final := ser.h.Sum(nil)
+	if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
+		return errors.SignatureError("hash mismatch")
+	}
+	return nil
+}
+
+// An seMDCWriter writes through to an io.WriteCloser while maintains a running
+// hash of the data written. On close, it emits an MDC packet containing the
+// running hash.
+type seMDCWriter struct {
+	w io.WriteCloser
+	h hash.Hash
+}
+
+func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
+	w.h.Write(buf)
+	return w.w.Write(buf)
+}
+
+func (w *seMDCWriter) Close() (err error) {
+	var buf [mdcTrailerSize]byte
+
+	buf[0] = mdcPacketTagByte
+	buf[1] = sha1.Size
+	w.h.Write(buf[:2])
+	digest := w.h.Sum(nil)
+	copy(buf[2:], digest)
+
+	_, err = w.w.Write(buf[:])
+	if err != nil {
+		return
+	}
+	return w.w.Close()
+}
+
+// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
+type noOpCloser struct {
+	w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+	return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+	return nil
+}
+
+// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
+// to w and returns a WriteCloser to which the to-be-encrypted packets can be
+// written.
+func SerializeSymmetricallyEncrypted(w io.Writer, rand io.Reader, c CipherFunction, key []byte) (contents io.WriteCloser, err error) {
+	if c.KeySize() != len(key) {
+		return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
+	}
+	writeCloser := noOpCloser{w}
+	ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
+	if err != nil {
+		return
+	}
+
+	_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
+	if err != nil {
+		return
+	}
+
+	block := c.new(key)
+	blockSize := block.BlockSize()
+	iv := make([]byte, blockSize)
+	_, err = rand.Read(iv)
+	if err != nil {
+		return
+	}
+	s, prefix := cipher.NewOCFBEncrypter(block, iv, cipher.OCFBNoResync)
+	_, err = ciphertext.Write(prefix)
+	if err != nil {
+		return
+	}
+	plaintext := cipher.StreamWriter{S: s, W: ciphertext}
+
+	h := sha1.New()
+	h.Write(iv)
+	h.Write(iv[blockSize-2:])
+	contents = &seMDCWriter{w: plaintext, h: h}
+	return
+}
diff --git a/openpgp/packet/symmetrically_encrypted_test.go b/openpgp/packet/symmetrically_encrypted_test.go
new file mode 100644
index 0000000..32ccff2
--- /dev/null
+++ b/openpgp/packet/symmetrically_encrypted_test.go
@@ -0,0 +1,124 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+// TestReader wraps a []byte and returns reads of a specific length.
+type testReader struct {
+	data   []byte
+	stride int
+}
+
+func (t *testReader) Read(buf []byte) (n int, err error) {
+	n = t.stride
+	if n > len(t.data) {
+		n = len(t.data)
+	}
+	if n > len(buf) {
+		n = len(buf)
+	}
+	copy(buf, t.data)
+	t.data = t.data[n:]
+	if len(t.data) == 0 {
+		err = io.EOF
+	}
+	return
+}
+
+func testMDCReader(t *testing.T) {
+	mdcPlaintext, _ := hex.DecodeString(mdcPlaintextHex)
+
+	for stride := 1; stride < len(mdcPlaintext)/2; stride++ {
+		r := &testReader{data: mdcPlaintext, stride: stride}
+		mdcReader := &seMDCReader{in: r, h: sha1.New()}
+		body, err := ioutil.ReadAll(mdcReader)
+		if err != nil {
+			t.Errorf("stride: %d, error: %s", stride, err)
+			continue
+		}
+		if !bytes.Equal(body, mdcPlaintext[:len(mdcPlaintext)-22]) {
+			t.Errorf("stride: %d: bad contents %x", stride, body)
+			continue
+		}
+
+		err = mdcReader.Close()
+		if err != nil {
+			t.Errorf("stride: %d, error on Close: %s", stride, err)
+		}
+	}
+
+	mdcPlaintext[15] ^= 80
+
+	r := &testReader{data: mdcPlaintext, stride: 2}
+	mdcReader := &seMDCReader{in: r, h: sha1.New()}
+	_, err := ioutil.ReadAll(mdcReader)
+	if err != nil {
+		t.Errorf("corruption test, error: %s", err)
+		return
+	}
+	err = mdcReader.Close()
+	if err == nil {
+		t.Error("corruption: no error")
+	} else if _, ok := err.(*errors.SignatureError); !ok {
+		t.Errorf("corruption: expected SignatureError, got: %s", err)
+	}
+}
+
+const mdcPlaintextHex = "a302789c3b2d93c4e0eb9aba22283539b3203335af44a134afb800c849cb4c4de10200aff40b45d31432c80cb384299a0655966d6939dfdeed1dddf980"
+
+func TestSerialize(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	c := CipherAES128
+	key := make([]byte, c.KeySize())
+
+	w, err := SerializeSymmetricallyEncrypted(buf, rand.Reader, c, key)
+	if err != nil {
+		t.Errorf("error from SerializeSymmetricallyEncrypted: %s", err)
+		return
+	}
+
+	contents := []byte("hello world\n")
+
+	w.Write(contents)
+	w.Close()
+
+	p, err := Read(buf)
+	if err != nil {
+		t.Errorf("error from Read: %s", err)
+		return
+	}
+
+	se, ok := p.(*SymmetricallyEncrypted)
+	if !ok {
+		t.Errorf("didn't read a *SymmetricallyEncrypted")
+		return
+	}
+
+	r, err := se.Decrypt(c, key)
+	if err != nil {
+		t.Errorf("error from Decrypt: %s", err)
+		return
+	}
+
+	contentsCopy := bytes.NewBuffer(nil)
+	_, err = io.Copy(contentsCopy, r)
+	if err != nil {
+		t.Errorf("error from io.Copy: %s", err)
+		return
+	}
+	if !bytes.Equal(contentsCopy.Bytes(), contents) {
+		t.Errorf("contents not equal got: %x want: %x", contentsCopy.Bytes(), contents)
+	}
+}
diff --git a/openpgp/packet/userid.go b/openpgp/packet/userid.go
new file mode 100644
index 0000000..d6bea7d
--- /dev/null
+++ b/openpgp/packet/userid.go
@@ -0,0 +1,160 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"io"
+	"io/ioutil"
+	"strings"
+)
+
+// UserId contains text that is intended to represent the name and email
+// address of the key holder. See RFC 4880, section 5.11. By convention, this
+// takes the form "Full Name (Comment) <email@example.com>"
+type UserId struct {
+	Id string // By convention, this takes the form "Full Name (Comment) <email@example.com>" which is split out in the fields below.
+
+	Name, Comment, Email string
+}
+
+func hasInvalidCharacters(s string) bool {
+	for _, c := range s {
+		switch c {
+		case '(', ')', '<', '>', 0:
+			return true
+		}
+	}
+	return false
+}
+
+// NewUserId returns a UserId or nil if any of the arguments contain invalid
+// characters. The invalid characters are '\x00', '(', ')', '<' and '>'
+func NewUserId(name, comment, email string) *UserId {
+	// RFC 4880 doesn't deal with the structure of userid strings; the
+	// name, comment and email form is just a convention. However, there's
+	// no convention about escaping the metacharacters and GPG just refuses
+	// to create user ids where, say, the name contains a '('. We mirror
+	// this behaviour.
+
+	if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) {
+		return nil
+	}
+
+	uid := new(UserId)
+	uid.Name, uid.Comment, uid.Email = name, comment, email
+	uid.Id = name
+	if len(comment) > 0 {
+		if len(uid.Id) > 0 {
+			uid.Id += " "
+		}
+		uid.Id += "("
+		uid.Id += comment
+		uid.Id += ")"
+	}
+	if len(email) > 0 {
+		if len(uid.Id) > 0 {
+			uid.Id += " "
+		}
+		uid.Id += "<"
+		uid.Id += email
+		uid.Id += ">"
+	}
+	return uid
+}
+
+func (uid *UserId) parse(r io.Reader) (err error) {
+	// RFC 4880, section 5.11
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return
+	}
+	uid.Id = string(b)
+	uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id)
+	return
+}
+
+// Serialize marshals uid to w in the form of an OpenPGP packet, including
+// header.
+func (uid *UserId) Serialize(w io.Writer) error {
+	err := serializeHeader(w, packetTypeUserId, len(uid.Id))
+	if err != nil {
+		return err
+	}
+	_, err = w.Write([]byte(uid.Id))
+	return err
+}
+
+// parseUserId extracts the name, comment and email from a user id string that
+// is formatted as "Full Name (Comment) <email@example.com>".
+func parseUserId(id string) (name, comment, email string) {
+	var n, c, e struct {
+		start, end int
+	}
+	var state int
+
+	for offset, rune := range id {
+		switch state {
+		case 0:
+			// Entering name
+			n.start = offset
+			state = 1
+			fallthrough
+		case 1:
+			// In name
+			if rune == '(' {
+				state = 2
+				n.end = offset
+			} else if rune == '<' {
+				state = 5
+				n.end = offset
+			}
+		case 2:
+			// Entering comment
+			c.start = offset
+			state = 3
+			fallthrough
+		case 3:
+			// In comment
+			if rune == ')' {
+				state = 4
+				c.end = offset
+			}
+		case 4:
+			// Between comment and email
+			if rune == '<' {
+				state = 5
+			}
+		case 5:
+			// Entering email
+			e.start = offset
+			state = 6
+			fallthrough
+		case 6:
+			// In email
+			if rune == '>' {
+				state = 7
+				e.end = offset
+			}
+		default:
+			// After email
+		}
+	}
+	switch state {
+	case 1:
+		// ended in the name
+		n.end = len(id)
+	case 3:
+		// ended in comment
+		c.end = len(id)
+	case 6:
+		// ended in email
+		e.end = len(id)
+	}
+
+	name = strings.TrimSpace(id[n.start:n.end])
+	comment = strings.TrimSpace(id[c.start:c.end])
+	email = strings.TrimSpace(id[e.start:e.end])
+	return
+}
diff --git a/openpgp/packet/userid_test.go b/openpgp/packet/userid_test.go
new file mode 100644
index 0000000..2968193
--- /dev/null
+++ b/openpgp/packet/userid_test.go
@@ -0,0 +1,87 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packet
+
+import (
+	"testing"
+)
+
+var userIdTests = []struct {
+	id                   string
+	name, comment, email string
+}{
+	{"", "", "", ""},
+	{"John Smith", "John Smith", "", ""},
+	{"John Smith ()", "John Smith", "", ""},
+	{"John Smith () <>", "John Smith", "", ""},
+	{"(comment", "", "comment", ""},
+	{"(comment)", "", "comment", ""},
+	{"<email", "", "", "email"},
+	{"<email>   sdfk", "", "", "email"},
+	{"  John Smith  (  Comment ) asdkflj < email > lksdfj", "John Smith", "Comment", "email"},
+	{"  John Smith  < email > lksdfj", "John Smith", "", "email"},
+	{"(<foo", "", "<foo", ""},
+	{"René Descartes (العربي)", "René Descartes", "العربي", ""},
+}
+
+func TestParseUserId(t *testing.T) {
+	for i, test := range userIdTests {
+		name, comment, email := parseUserId(test.id)
+		if name != test.name {
+			t.Errorf("%d: name mismatch got:%s want:%s", i, name, test.name)
+		}
+		if comment != test.comment {
+			t.Errorf("%d: comment mismatch got:%s want:%s", i, comment, test.comment)
+		}
+		if email != test.email {
+			t.Errorf("%d: email mismatch got:%s want:%s", i, email, test.email)
+		}
+	}
+}
+
+var newUserIdTests = []struct {
+	name, comment, email, id string
+}{
+	{"foo", "", "", "foo"},
+	{"", "bar", "", "(bar)"},
+	{"", "", "baz", "<baz>"},
+	{"foo", "bar", "", "foo (bar)"},
+	{"foo", "", "baz", "foo <baz>"},
+	{"", "bar", "baz", "(bar) <baz>"},
+	{"foo", "bar", "baz", "foo (bar) <baz>"},
+}
+
+func TestNewUserId(t *testing.T) {
+	for i, test := range newUserIdTests {
+		uid := NewUserId(test.name, test.comment, test.email)
+		if uid == nil {
+			t.Errorf("#%d: returned nil", i)
+			continue
+		}
+		if uid.Id != test.id {
+			t.Errorf("#%d: got '%s', want '%s'", i, uid.Id, test.id)
+		}
+	}
+}
+
+var invalidNewUserIdTests = []struct {
+	name, comment, email string
+}{
+	{"foo(", "", ""},
+	{"foo<", "", ""},
+	{"", "bar)", ""},
+	{"", "bar<", ""},
+	{"", "", "baz>"},
+	{"", "", "baz)"},
+	{"", "", "baz\x00"},
+}
+
+func TestNewUserIdWithInvalidInput(t *testing.T) {
+	for i, test := range invalidNewUserIdTests {
+		if uid := NewUserId(test.name, test.comment, test.email); uid != nil {
+			t.Errorf("#%d: returned non-nil value: %#v", i, uid)
+		}
+	}
+}
diff --git a/openpgp/read.go b/openpgp/read.go
new file mode 100644
index 0000000..1eec1b1
--- /dev/null
+++ b/openpgp/read.go
@@ -0,0 +1,414 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package openpgp implements high level operations on OpenPGP messages.
+package openpgp
+
+import (
+	"code.google.com/p/go.crypto/openpgp/armor"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/packet"
+	"crypto"
+	_ "crypto/sha256"
+	"hash"
+	"io"
+	"strconv"
+)
+
+// SignatureType is the armor type for a PGP signature.
+var SignatureType = "PGP SIGNATURE"
+
+// readArmored reads an armored block with the given type.
+func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) {
+	block, err := armor.Decode(r)
+	if err != nil {
+		return
+	}
+
+	if block.Type != expectedType {
+		return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type)
+	}
+
+	return block.Body, nil
+}
+
+// MessageDetails contains the result of parsing an OpenPGP encrypted and/or
+// signed message.
+type MessageDetails struct {
+	IsEncrypted              bool                // true if the message was encrypted.
+	EncryptedToKeyIds        []uint64            // the list of recipient key ids.
+	IsSymmetricallyEncrypted bool                // true if a passphrase could have decrypted the message.
+	DecryptedWith            Key                 // the private key used to decrypt the message, if any.
+	IsSigned                 bool                // true if the message is signed.
+	SignedByKeyId            uint64              // the key id of the signer, if any.
+	SignedBy                 *Key                // the key of the signer, if available.
+	LiteralData              *packet.LiteralData // the metadata of the contents
+	UnverifiedBody           io.Reader           // the contents of the message.
+
+	// If IsSigned is true and SignedBy is non-zero then the signature will
+	// be verified as UnverifiedBody is read. The signature cannot be
+	// checked until the whole of UnverifiedBody is read so UnverifiedBody
+	// must be consumed until EOF before the data can trusted. Even if a
+	// message isn't signed (or the signer is unknown) the data may contain
+	// an authentication code that is only checked once UnverifiedBody has
+	// been consumed. Once EOF has been seen, the following fields are
+	// valid. (An authentication code failure is reported as a
+	// SignatureError error when reading from UnverifiedBody.)
+	SignatureError error             // nil if the signature is good.
+	Signature      *packet.Signature // the signature packet itself.
+
+	decrypted io.ReadCloser
+}
+
+// A PromptFunction is used as a callback by functions that may need to decrypt
+// a private key, or prompt for a passphrase. It is called with a list of
+// acceptable, encrypted private keys and a boolean that indicates whether a
+// passphrase is usable. It should either decrypt a private key or return a
+// passphrase to try. If the decrypted private key or given passphrase isn't
+// correct, the function will be called again, forever. Any error returned will
+// be passed up.
+type PromptFunction func(keys []Key, symmetric bool) ([]byte, error)
+
+// A keyEnvelopePair is used to store a private key with the envelope that
+// contains a symmetric key, encrypted with that key.
+type keyEnvelopePair struct {
+	key          Key
+	encryptedKey *packet.EncryptedKey
+}
+
+// ReadMessage parses an OpenPGP message that may be signed and/or encrypted.
+// The given KeyRing should contain both public keys (for signature
+// verification) and, possibly encrypted, private keys for decrypting.
+func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction) (md *MessageDetails, err error) {
+	var p packet.Packet
+
+	var symKeys []*packet.SymmetricKeyEncrypted
+	var pubKeys []keyEnvelopePair
+	var se *packet.SymmetricallyEncrypted
+
+	packets := packet.NewReader(r)
+	md = new(MessageDetails)
+	md.IsEncrypted = true
+
+	// The message, if encrypted, starts with a number of packets
+	// containing an encrypted decryption key. The decryption key is either
+	// encrypted to a public key, or with a passphrase. This loop
+	// collects these packets.
+ParsePackets:
+	for {
+		p, err = packets.Next()
+		if err != nil {
+			return nil, err
+		}
+		switch p := p.(type) {
+		case *packet.SymmetricKeyEncrypted:
+			// This packet contains the decryption key encrypted with a passphrase.
+			md.IsSymmetricallyEncrypted = true
+			symKeys = append(symKeys, p)
+		case *packet.EncryptedKey:
+			// This packet contains the decryption key encrypted to a public key.
+			md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId)
+			switch p.Algo {
+			case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal:
+				break
+			default:
+				continue
+			}
+			var keys []Key
+			if p.KeyId == 0 {
+				keys = keyring.DecryptionKeys()
+			} else {
+				keys = keyring.KeysById(p.KeyId)
+			}
+			for _, k := range keys {
+				pubKeys = append(pubKeys, keyEnvelopePair{k, p})
+			}
+		case *packet.SymmetricallyEncrypted:
+			se = p
+			break ParsePackets
+		case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature:
+			// This message isn't encrypted.
+			if len(symKeys) != 0 || len(pubKeys) != 0 {
+				return nil, errors.StructuralError("key material not followed by encrypted message")
+			}
+			packets.Unread(p)
+			return readSignedMessage(packets, nil, keyring)
+		}
+	}
+
+	var candidates []Key
+	var decrypted io.ReadCloser
+
+	// Now that we have the list of encrypted keys we need to decrypt at
+	// least one of them or, if we cannot, we need to call the prompt
+	// function so that it can decrypt a key or give us a passphrase.
+FindKey:
+	for {
+		// See if any of the keys already have a private key available
+		candidates = candidates[:0]
+		candidateFingerprints := make(map[string]bool)
+
+		for _, pk := range pubKeys {
+			if pk.key.PrivateKey == nil {
+				continue
+			}
+			if !pk.key.PrivateKey.Encrypted {
+				if len(pk.encryptedKey.Key) == 0 {
+					pk.encryptedKey.Decrypt(pk.key.PrivateKey)
+				}
+				if len(pk.encryptedKey.Key) == 0 {
+					continue
+				}
+				decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key)
+				if err != nil && err != errors.ErrKeyIncorrect {
+					return nil, err
+				}
+				if decrypted != nil {
+					md.DecryptedWith = pk.key
+					break FindKey
+				}
+			} else {
+				fpr := string(pk.key.PublicKey.Fingerprint[:])
+				if v := candidateFingerprints[fpr]; v {
+					continue
+				}
+				candidates = append(candidates, pk.key)
+				candidateFingerprints[fpr] = true
+			}
+		}
+
+		if len(candidates) == 0 && len(symKeys) == 0 {
+			return nil, errors.ErrKeyIncorrect
+		}
+
+		if prompt == nil {
+			return nil, errors.ErrKeyIncorrect
+		}
+
+		passphrase, err := prompt(candidates, len(symKeys) != 0)
+		if err != nil {
+			return nil, err
+		}
+
+		// Try the symmetric passphrase first
+		if len(symKeys) != 0 && passphrase != nil {
+			for _, s := range symKeys {
+				err = s.Decrypt(passphrase)
+				if err == nil && !s.Encrypted {
+					decrypted, err = se.Decrypt(s.CipherFunc, s.Key)
+					if err != nil && err != errors.ErrKeyIncorrect {
+						return nil, err
+					}
+					if decrypted != nil {
+						break FindKey
+					}
+				}
+
+			}
+		}
+	}
+
+	md.decrypted = decrypted
+	packets.Push(decrypted)
+	return readSignedMessage(packets, md, keyring)
+}
+
+// readSignedMessage reads a possibly signed message if mdin is non-zero then
+// that structure is updated and returned. Otherwise a fresh MessageDetails is
+// used.
+func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing) (md *MessageDetails, err error) {
+	if mdin == nil {
+		mdin = new(MessageDetails)
+	}
+	md = mdin
+
+	var p packet.Packet
+	var h hash.Hash
+	var wrappedHash hash.Hash
+FindLiteralData:
+	for {
+		p, err = packets.Next()
+		if err != nil {
+			return nil, err
+		}
+		switch p := p.(type) {
+		case *packet.Compressed:
+			packets.Push(p.Body)
+		case *packet.OnePassSignature:
+			if !p.IsLast {
+				return nil, errors.UnsupportedError("nested signatures")
+			}
+
+			h, wrappedHash, err = hashForSignature(p.Hash, p.SigType)
+			if err != nil {
+				md = nil
+				return
+			}
+
+			md.IsSigned = true
+			md.SignedByKeyId = p.KeyId
+			keys := keyring.KeysById(p.KeyId)
+			for i, key := range keys {
+				if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+					continue
+				}
+				md.SignedBy = &keys[i]
+				break
+			}
+		case *packet.LiteralData:
+			md.LiteralData = p
+			break FindLiteralData
+		}
+	}
+
+	if md.SignedBy != nil {
+		md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md}
+	} else if md.decrypted != nil {
+		md.UnverifiedBody = checkReader{md}
+	} else {
+		md.UnverifiedBody = md.LiteralData.Body
+	}
+
+	return md, nil
+}
+
+// hashForSignature returns a pair of hashes that can be used to verify a
+// signature. The signature may specify that the contents of the signed message
+// should be preprocessed (i.e. to normalize line endings). Thus this function
+// returns two hashes. The second should be used to hash the message itself and
+// performs any needed preprocessing.
+func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
+	h := hashId.New()
+	if h == nil {
+		return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
+	}
+
+	switch sigType {
+	case packet.SigTypeBinary:
+		return h, h, nil
+	case packet.SigTypeText:
+		return h, NewCanonicalTextHash(h), nil
+	}
+
+	return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType)))
+}
+
+// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF
+// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger
+// MDC checks.
+type checkReader struct {
+	md *MessageDetails
+}
+
+func (cr checkReader) Read(buf []byte) (n int, err error) {
+	n, err = cr.md.LiteralData.Body.Read(buf)
+	if err == io.EOF {
+		mdcErr := cr.md.decrypted.Close()
+		if mdcErr != nil {
+			err = mdcErr
+		}
+	}
+	return
+}
+
+// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes
+// the data as it is read. When it sees an EOF from the underlying io.Reader
+// it parses and checks a trailing Signature packet and triggers any MDC checks.
+type signatureCheckReader struct {
+	packets        *packet.Reader
+	h, wrappedHash hash.Hash
+	md             *MessageDetails
+}
+
+func (scr *signatureCheckReader) Read(buf []byte) (n int, err error) {
+	n, err = scr.md.LiteralData.Body.Read(buf)
+	scr.wrappedHash.Write(buf[:n])
+	if err == io.EOF {
+		var p packet.Packet
+		p, scr.md.SignatureError = scr.packets.Next()
+		if scr.md.SignatureError != nil {
+			return
+		}
+
+		var ok bool
+		if scr.md.Signature, ok = p.(*packet.Signature); !ok {
+			scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature")
+			return
+		}
+
+		scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature)
+
+		// The SymmetricallyEncrypted packet, if any, might have an
+		// unsigned hash of its own. In order to check this we need to
+		// close that Reader.
+		if scr.md.decrypted != nil {
+			mdcErr := scr.md.decrypted.Close()
+			if mdcErr != nil {
+				err = mdcErr
+			}
+		}
+	}
+	return
+}
+
+// CheckDetachedSignature takes a signed file and a detached signature and
+// returns the signer if the signature is valid. If the signer isn't known,
+// ErrUnknownIssuer is returned.
+func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+	p, err := packet.Read(signature)
+	if err != nil {
+		return
+	}
+
+	sig, ok := p.(*packet.Signature)
+	if !ok {
+		return nil, errors.StructuralError("non signature packet found")
+	}
+
+	if sig.IssuerKeyId == nil {
+		return nil, errors.StructuralError("signature doesn't have an issuer")
+	}
+
+	keys := keyring.KeysById(*sig.IssuerKeyId)
+	if len(keys) == 0 {
+		return nil, errors.ErrUnknownIssuer
+	}
+
+	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+	if err != nil {
+		return
+	}
+
+	_, err = io.Copy(wrappedHash, signed)
+	if err != nil && err != io.EOF {
+		return
+	}
+
+	for _, key := range keys {
+		if key.SelfSignature.FlagsValid && !key.SelfSignature.FlagSign {
+			continue
+		}
+		err = key.PublicKey.VerifySignature(h, sig)
+		if err == nil {
+			return key.Entity, nil
+		}
+	}
+
+	if err != nil {
+		return
+	}
+
+	return nil, errors.ErrUnknownIssuer
+}
+
+// CheckArmoredDetachedSignature performs the same actions as
+// CheckDetachedSignature but expects the signature to be armored.
+func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) {
+	body, err := readArmored(signature, SignatureType)
+	if err != nil {
+		return
+	}
+
+	return CheckDetachedSignature(keyring, signed, body)
+}
diff --git a/openpgp/read_test.go b/openpgp/read_test.go
new file mode 100644
index 0000000..18fcfe6
--- /dev/null
+++ b/openpgp/read_test.go
@@ -0,0 +1,372 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	_ "crypto/sha512"
+	"encoding/hex"
+	"io"
+	"io/ioutil"
+	"testing"
+)
+
+func readerFromHex(s string) io.Reader {
+	data, err := hex.DecodeString(s)
+	if err != nil {
+		panic("readerFromHex: bad input")
+	}
+	return bytes.NewBuffer(data)
+}
+
+func TestReadKeyRing(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B {
+		t.Errorf("bad keyring: %#v", kring)
+	}
+}
+
+func TestRereadKeyRing(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+	if err != nil {
+		t.Errorf("error in initial parse: %s", err)
+		return
+	}
+	out := new(bytes.Buffer)
+	err = kring[0].Serialize(out)
+	if err != nil {
+		t.Errorf("error in serialization: %s", err)
+		return
+	}
+	kring, err = ReadKeyRing(out)
+	if err != nil {
+		t.Errorf("error in second parse: %s", err)
+		return
+	}
+
+	if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB {
+		t.Errorf("bad keyring: %#v", kring)
+	}
+}
+
+func TestReadPrivateKeyRing(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil {
+		t.Errorf("bad keyring: %#v", kring)
+	}
+}
+
+func TestReadDSAKey(t *testing.T) {
+	kring, err := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x0CCC0360 {
+		t.Errorf("bad parse: %#v", kring)
+	}
+}
+
+func TestDSAHashTruncatation(t *testing.T) {
+	// dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo
+	// SHA512 in order to require DSA hash truncation to verify correctly.
+	_, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512))
+	if err != nil {
+		t.Error(err)
+	}
+}
+
+func TestGetKeyById(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+	keys := kring.KeysById(0xa34d7e18c20c31bb)
+	if len(keys) != 1 || keys[0].Entity != kring[0] {
+		t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+	}
+
+	keys = kring.KeysById(0xfd94408d4543314f)
+	if len(keys) != 1 || keys[0].Entity != kring[0] {
+		t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys)
+	}
+}
+
+func checkSignedMessage(t *testing.T, signedHex, expected string) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+
+	md, err := ReadMessage(readerFromHex(signedHex), kring, nil)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.IsSymmetricallyEncrypted {
+		t.Errorf("bad MessageDetails: %#v", md)
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error reading UnverifiedBody: %s", err)
+	}
+	if string(contents) != expected {
+		t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+	}
+	if md.SignatureError != nil || md.Signature == nil {
+		t.Errorf("failed to validate: %s", md.SignatureError)
+	}
+}
+
+func TestSignedMessage(t *testing.T) {
+	checkSignedMessage(t, signedMessageHex, signedInput)
+}
+
+func TestTextSignedMessage(t *testing.T) {
+	checkSignedMessage(t, signedTextMessageHex, signedTextInput)
+}
+
+var signedEncryptedMessageTests = []struct {
+	keyRingHex       string
+	messageHex       string
+	signedByKeyId    uint64
+	encryptedToKeyId uint64
+}{
+	{
+		testKeys1And2PrivateHex,
+		signedEncryptedMessageHex,
+		0xa34d7e18c20c31bb,
+		0x2a67d68660df41c7,
+	},
+	{
+		dsaElGamalTestKeysHex,
+		signedEncryptedMessage2Hex,
+		0x33af447ccd759b09,
+		0xcf6a7abcd43e3673,
+	},
+}
+
+func TestSignedEncryptedMessage(t *testing.T) {
+	for i, test := range signedEncryptedMessageTests {
+		expected := "Signed and encrypted message\n"
+		kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+		prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+			if symmetric {
+				t.Errorf("prompt: message was marked as symmetrically encrypted")
+				return nil, errors.ErrKeyIncorrect
+			}
+
+			if len(keys) == 0 {
+				t.Error("prompt: no keys requested")
+				return nil, errors.ErrKeyIncorrect
+			}
+
+			err := keys[0].PrivateKey.Decrypt([]byte("passphrase"))
+			if err != nil {
+				t.Errorf("prompt: error decrypting key: %s", err)
+				return nil, errors.ErrKeyIncorrect
+			}
+
+			return nil, nil
+		}
+
+		md, err := ReadMessage(readerFromHex(test.messageHex), kring, prompt)
+		if err != nil {
+			t.Errorf("#%d: error reading message: %s", i, err)
+			return
+		}
+
+		if !md.IsSigned || md.SignedByKeyId != test.signedByKeyId || md.SignedBy == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != test.encryptedToKeyId {
+			t.Errorf("#%d: bad MessageDetails: %#v", i, md)
+		}
+
+		contents, err := ioutil.ReadAll(md.UnverifiedBody)
+		if err != nil {
+			t.Errorf("#%d: error reading UnverifiedBody: %s", i, err)
+		}
+		if string(contents) != expected {
+			t.Errorf("#%d: bad UnverifiedBody got:%s want:%s", i, string(contents), expected)
+		}
+
+		if md.SignatureError != nil || md.Signature == nil {
+			t.Errorf("#%d: failed to validate: %s", i, md.SignatureError)
+		}
+	}
+}
+
+func TestUnspecifiedRecipient(t *testing.T) {
+	expected := "Recipient unspecified\n"
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+
+	md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil)
+	if err != nil {
+		t.Errorf("error reading message: %s", err)
+		return
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error reading UnverifiedBody: %s", err)
+	}
+	if string(contents) != expected {
+		t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected)
+	}
+}
+
+func TestSymmetricallyEncrypted(t *testing.T) {
+	expected := "Symmetrically encrypted.\n"
+
+	prompt := func(keys []Key, symmetric bool) ([]byte, error) {
+		if len(keys) != 0 {
+			t.Errorf("prompt: len(keys) = %d (want 0)", len(keys))
+		}
+
+		if !symmetric {
+			t.Errorf("symmetric is not set")
+		}
+
+		return []byte("password"), nil
+	}
+
+	md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt)
+	if err != nil {
+		t.Errorf("ReadMessage: %s", err)
+		return
+	}
+
+	contents, err := ioutil.ReadAll(md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("ReadAll: %s", err)
+	}
+
+	expectedCreationTime := uint32(1295992998)
+	if md.LiteralData.Time != expectedCreationTime {
+		t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreationTime)
+	}
+
+	if string(contents) != expected {
+		t.Errorf("contents got: %s want: %s", string(contents), expected)
+	}
+}
+
+func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string, expectedSignerKeyId uint64) {
+	signed := bytes.NewBufferString(sigInput)
+	signer, err := CheckDetachedSignature(kring, signed, signature)
+	if err != nil {
+		t.Errorf("%s: signature error: %s", tag, err)
+		return
+	}
+	if signer == nil {
+		t.Errorf("%s: signer is nil", tag)
+		return
+	}
+	if signer.PrimaryKey.KeyId != expectedSignerKeyId {
+		t.Errorf("%s: wrong signer got:%x want:%x", tag, signer.PrimaryKey.KeyId, expectedSignerKeyId)
+	}
+}
+
+func TestDetachedSignature(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex))
+	testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId)
+	testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId)
+}
+
+func TestDetachedSignatureDSA(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex))
+	testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId)
+}
+
+func TestReadingArmoredPrivateKey(t *testing.T) {
+	el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock))
+	if err != nil {
+		t.Error(err)
+	}
+	if len(el) != 1 {
+		t.Errorf("got %d entities, wanted 1\n", len(el))
+	}
+}
+
+func TestNoArmoredData(t *testing.T) {
+	_, err := ReadArmoredKeyRing(bytes.NewBufferString("foo"))
+	if _, ok := err.(errors.InvalidArgumentError); !ok {
+		t.Errorf("error was not an InvalidArgumentError: %s", err)
+	}
+}
+
+const testKey1KeyId = 0xA34D7E18C20C31BB
+const testKey3KeyId = 0x338934250CCC0360
+
+const signedInput = "Signed message\nline 2\nline 3\n"
+const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
+
+const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b"
+
+const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77"
+
+const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39"
+
+const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83"
+
+const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003"
+
+const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000"
+
+const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000"
+
+const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300"
+
+const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200"
+
+const signedEncryptedMessageHex = "848c032a67d68660df41c70103ff5789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8d2c03b018bd210b1d3791e1aba74b0f1034e122ab72e760492c192383cf5e20b5628bd043272d63df9b923f147eb6091cd897553204832aba48fec54aa447547bb16305a1024713b90e77fd0065f1918271947549205af3c74891af22ee0b56cd29bfec6d6e351901cd4ab3ece7c486f1e32a792d4e474aed98ee84b3f591c7dff37b64e0ecd68fd036d517e412dcadf85840ce184ad7921ad446c4ee28db80447aea1ca8d4f574db4d4e37688158ddd19e14ee2eab4873d46947d65d14a23e788d912cf9a19624ca7352469b72a83866b7c23cb5ace3deab3c7018061b0ba0f39ed2befe27163e5083cf9b8271e3e3d52cc7ad6e2a3bd81d4c3d7022f8d"
+
+const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3"
+
+const symmetricallyEncryptedCompressedHex = "8c0d04030302eb4a03808145d0d260c92f714339e13de5a79881216431925bf67ee2898ea61815f07894cd0703c50d0a76ef64d482196f47a8bc729af9b80bb6"
+
+const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794"
+
+const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.10 (GNU/Linux)
+
+lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp
+idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn
+vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB
+AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X
+0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL
+IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk
+VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn
+gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9
+TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx
+q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz
+dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA
+CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1
+ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+
+eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid
+AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV
+bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK
+/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA
+A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX
+TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc
+lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6
+rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN
+oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8
+QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU
+nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC
+AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp
+BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad
+AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL
+VrM0m72/jnpKo04=
+=zNCn
+-----END PGP PRIVATE KEY BLOCK-----`
+
+const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003`
diff --git a/openpgp/s2k/s2k.go b/openpgp/s2k/s2k.go
new file mode 100644
index 0000000..c8b9bbd
--- /dev/null
+++ b/openpgp/s2k/s2k.go
@@ -0,0 +1,183 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package s2k implements the various OpenPGP string-to-key transforms as
+// specified in RFC 4800 section 3.7.1.
+package s2k
+
+import (
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"crypto"
+	"hash"
+	"io"
+	"strconv"
+)
+
+// Simple writes to out the result of computing the Simple S2K function (RFC
+// 4880, section 3.7.1.1) using the given hash and input passphrase.
+func Simple(out []byte, h hash.Hash, in []byte) {
+	Salted(out, h, in, nil)
+}
+
+var zero [1]byte
+
+// Salted writes to out the result of computing the Salted S2K function (RFC
+// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
+func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
+	done := 0
+	var digest []byte
+
+	for i := 0; done < len(out); i++ {
+		h.Reset()
+		for j := 0; j < i; j++ {
+			h.Write(zero[:])
+		}
+		h.Write(salt)
+		h.Write(in)
+		digest = h.Sum(digest[:0])
+		n := copy(out[done:], digest)
+		done += n
+	}
+}
+
+// Iterated writes to out the result of computing the Iterated and Salted S2K
+// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
+// salt and iteration count.
+func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
+	combined := make([]byte, len(in)+len(salt))
+	copy(combined, salt)
+	copy(combined[len(salt):], in)
+
+	if count < len(combined) {
+		count = len(combined)
+	}
+
+	done := 0
+	var digest []byte
+	for i := 0; done < len(out); i++ {
+		h.Reset()
+		for j := 0; j < i; j++ {
+			h.Write(zero[:])
+		}
+		written := 0
+		for written < count {
+			if written+len(combined) > count {
+				todo := count - written
+				h.Write(combined[:todo])
+				written = count
+			} else {
+				h.Write(combined)
+				written += len(combined)
+			}
+		}
+		digest = h.Sum(digest[:0])
+		n := copy(out[done:], digest)
+		done += n
+	}
+}
+
+// Parse reads a binary specification for a string-to-key transformation from r
+// and returns a function which performs that transform.
+func Parse(r io.Reader) (f func(out, in []byte), err error) {
+	var buf [9]byte
+
+	_, err = io.ReadFull(r, buf[:2])
+	if err != nil {
+		return
+	}
+
+	hash, ok := HashIdToHash(buf[1])
+	if !ok {
+		return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
+	}
+	h := hash.New()
+	if h == nil {
+		return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
+	}
+
+	switch buf[0] {
+	case 1:
+		f := func(out, in []byte) {
+			Simple(out, h, in)
+		}
+		return f, nil
+	case 2:
+		_, err = io.ReadFull(r, buf[:8])
+		if err != nil {
+			return
+		}
+		f := func(out, in []byte) {
+			Salted(out, h, in, buf[:8])
+		}
+		return f, nil
+	case 3:
+		_, err = io.ReadFull(r, buf[:9])
+		if err != nil {
+			return
+		}
+		count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
+		f := func(out, in []byte) {
+			Iterated(out, h, in, buf[:8], count)
+		}
+		return f, nil
+	}
+
+	return nil, errors.UnsupportedError("S2K function")
+}
+
+// Serialize salts and stretches the given passphrase and writes the resulting
+// key into key. It also serializes an S2K descriptor to w.
+func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte) error {
+	var buf [11]byte
+	buf[0] = 3 /* iterated and salted */
+	buf[1], _ = HashToHashId(crypto.SHA1)
+	salt := buf[2:10]
+	if _, err := io.ReadFull(rand, salt); err != nil {
+		return err
+	}
+	const count = 65536 // this is the default in gpg
+	buf[10] = 96        // 65536 iterations
+	if _, err := w.Write(buf[:]); err != nil {
+		return err
+	}
+
+	Iterated(key, crypto.SHA1.New(), passphrase, salt, count)
+	return nil
+}
+
+// hashToHashIdMapping contains pairs relating OpenPGP's hash identifier with
+// Go's crypto.Hash type. See RFC 4880, section 9.4.
+var hashToHashIdMapping = []struct {
+	id   byte
+	hash crypto.Hash
+}{
+	{1, crypto.MD5},
+	{2, crypto.SHA1},
+	{3, crypto.RIPEMD160},
+	{8, crypto.SHA256},
+	{9, crypto.SHA384},
+	{10, crypto.SHA512},
+	{11, crypto.SHA224},
+}
+
+// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
+// hash id.
+func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
+	for _, m := range hashToHashIdMapping {
+		if m.id == id {
+			return m.hash, true
+		}
+	}
+	return 0, false
+}
+
+// HashIdToHash returns an OpenPGP hash id which corresponds the given Hash.
+func HashToHashId(h crypto.Hash) (id byte, ok bool) {
+	for _, m := range hashToHashIdMapping {
+		if m.hash == h {
+			return m.id, true
+		}
+	}
+	return 0, false
+}
diff --git a/openpgp/s2k/s2k_test.go b/openpgp/s2k/s2k_test.go
new file mode 100644
index 0000000..3a094a1
--- /dev/null
+++ b/openpgp/s2k/s2k_test.go
@@ -0,0 +1,118 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package s2k
+
+import (
+	"bytes"
+	"crypto/rand"
+	"crypto/sha1"
+	"encoding/hex"
+	"testing"
+)
+
+var saltedTests = []struct {
+	in, out string
+}{
+	{"hello", "10295ac1"},
+	{"world", "ac587a5e"},
+	{"foo", "4dda8077"},
+	{"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"},
+	{"x", "f1d3f289"},
+	{"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"},
+}
+
+func TestSalted(t *testing.T) {
+	h := sha1.New()
+	salt := [4]byte{1, 2, 3, 4}
+
+	for i, test := range saltedTests {
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		Salted(out, h, []byte(test.in), salt[:])
+		if !bytes.Equal(expected, out) {
+			t.Errorf("#%d, got: %x want: %x", i, out, expected)
+		}
+	}
+}
+
+var iteratedTests = []struct {
+	in, out string
+}{
+	{"hello", "83126105"},
+	{"world", "6fa317f9"},
+	{"foo", "8fbc35b9"},
+	{"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"},
+	{"x", "5a684dfe"},
+	{"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"},
+}
+
+func TestIterated(t *testing.T) {
+	h := sha1.New()
+	salt := [4]byte{4, 3, 2, 1}
+
+	for i, test := range iteratedTests {
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		Iterated(out, h, []byte(test.in), salt[:], 31)
+		if !bytes.Equal(expected, out) {
+			t.Errorf("#%d, got: %x want: %x", i, out, expected)
+		}
+	}
+}
+
+var parseTests = []struct {
+	spec, in, out string
+}{
+	/* Simple with SHA1 */
+	{"0102", "hello", "aaf4c61d"},
+	/* Salted with SHA1 */
+	{"02020102030405060708", "hello", "f4f7d67e"},
+	/* Iterated with SHA1 */
+	{"03020102030405060708f1", "hello", "f2a57b7c"},
+}
+
+func TestParse(t *testing.T) {
+	for i, test := range parseTests {
+		spec, _ := hex.DecodeString(test.spec)
+		buf := bytes.NewBuffer(spec)
+		f, err := Parse(buf)
+		if err != nil {
+			t.Errorf("%d: Parse returned error: %s", i, err)
+			continue
+		}
+
+		expected, _ := hex.DecodeString(test.out)
+		out := make([]byte, len(expected))
+		f(out, []byte(test.in))
+		if !bytes.Equal(out, expected) {
+			t.Errorf("%d: output got: %x want: %x", i, out, expected)
+		}
+		if testing.Short() {
+			break
+		}
+	}
+}
+
+func TestSerialize(t *testing.T) {
+	buf := bytes.NewBuffer(nil)
+	key := make([]byte, 16)
+	passphrase := []byte("testing")
+	err := Serialize(buf, key, rand.Reader, passphrase)
+	if err != nil {
+		t.Errorf("failed to serialize: %s", err)
+		return
+	}
+
+	f, err := Parse(buf)
+	if err != nil {
+		t.Errorf("failed to reparse: %s", err)
+		return
+	}
+	key2 := make([]byte, len(key))
+	f(key2, passphrase)
+	if !bytes.Equal(key2, key) {
+		t.Errorf("keys don't match: %x (serialied) vs %x (parsed)", key, key2)
+	}
+}
diff --git a/openpgp/write.go b/openpgp/write.go
new file mode 100644
index 0000000..c93b26e
--- /dev/null
+++ b/openpgp/write.go
@@ -0,0 +1,315 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"code.google.com/p/go.crypto/openpgp/armor"
+	"code.google.com/p/go.crypto/openpgp/errors"
+	"code.google.com/p/go.crypto/openpgp/packet"
+	"code.google.com/p/go.crypto/openpgp/s2k"
+	"crypto"
+	"crypto/rand"
+	_ "crypto/sha256"
+	"hash"
+	"io"
+	"strconv"
+	"time"
+)
+
+// DetachSign signs message with the private key from signer (which must
+// already have been decrypted) and writes the signature to w.
+func DetachSign(w io.Writer, signer *Entity, message io.Reader) error {
+	return detachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// ArmoredDetachSign signs message with the private key from signer (which
+// must already have been decrypted) and writes an armored signature to w.
+func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader) (err error) {
+	return armoredDetachSign(w, signer, message, packet.SigTypeBinary)
+}
+
+// DetachSignText signs message (after canonicalising the line endings) with
+// the private key from signer (which must already have been decrypted) and
+// writes the signature to w.
+func DetachSignText(w io.Writer, signer *Entity, message io.Reader) error {
+	return detachSign(w, signer, message, packet.SigTypeText)
+}
+
+// ArmoredDetachSignText signs message (after canonicalising the line endings)
+// with the private key from signer (which must already have been decrypted)
+// and writes an armored signature to w.
+func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader) error {
+	return armoredDetachSign(w, signer, message, packet.SigTypeText)
+}
+
+func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err error) {
+	out, err := armor.Encode(w, SignatureType, nil)
+	if err != nil {
+		return
+	}
+	err = detachSign(out, signer, message, sigType)
+	if err != nil {
+		return
+	}
+	return out.Close()
+}
+
+func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType) (err error) {
+	if signer.PrivateKey == nil {
+		return errors.InvalidArgumentError("signing key doesn't have a private key")
+	}
+	if signer.PrivateKey.Encrypted {
+		return errors.InvalidArgumentError("signing key is encrypted")
+	}
+
+	sig := new(packet.Signature)
+	sig.SigType = sigType
+	sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo
+	sig.Hash = crypto.SHA256
+	sig.CreationTime = time.Now()
+	sig.IssuerKeyId = &signer.PrivateKey.KeyId
+
+	h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
+	if err != nil {
+		return
+	}
+	io.Copy(wrappedHash, message)
+
+	err = sig.Sign(rand.Reader, h, signer.PrivateKey)
+	if err != nil {
+		return
+	}
+
+	return sig.Serialize(w)
+}
+
+// FileHints contains metadata about encrypted files. This metadata is, itself,
+// encrypted.
+type FileHints struct {
+	// IsBinary can be set to hint that the contents are binary data.
+	IsBinary bool
+	// FileName hints at the name of the file that should be written. It's
+	// truncated to 255 bytes if longer. It may be empty to suggest that the
+	// file should not be written to disk. It may be equal to "_CONSOLE" to
+	// suggest the data should not be written to disk.
+	FileName string
+	// ModTime contains the modification time of the file, or the zero time if not applicable.
+	ModTime time.Time
+}
+
+// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase.
+// The resulting WriteCloser must be closed after the contents of the file have
+// been written.
+func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints) (plaintext io.WriteCloser, err error) {
+	if hints == nil {
+		hints = &FileHints{}
+	}
+
+	key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, rand.Reader, passphrase, packet.CipherAES128)
+	if err != nil {
+		return
+	}
+	w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, packet.CipherAES128, key)
+	if err != nil {
+		return
+	}
+	var epochSeconds uint32
+	if !hints.ModTime.IsZero() {
+		epochSeconds = uint32(hints.ModTime.Unix())
+	}
+	return packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
+}
+
+// intersectPreferences mutates and returns a prefix of a that contains only
+// the values in the intersection of a and b. The order of a is preserved.
+func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
+	var j int
+	for _, v := range a {
+		for _, v2 := range b {
+			if v == v2 {
+				a[j] = v
+				j++
+				break
+			}
+		}
+	}
+
+	return a[:j]
+}
+
+func hashToHashId(h crypto.Hash) uint8 {
+	v, ok := s2k.HashToHashId(h)
+	if !ok {
+		panic("tried to convert unknown hash")
+	}
+	return v
+}
+
+// Encrypt encrypts a message to a number of recipients and, optionally, signs
+// it. hints contains optional information, that is also encrypted, that aids
+// the recipients in processing the message. The resulting WriteCloser must
+// be closed after the contents of the file have been written.
+func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints) (plaintext io.WriteCloser, err error) {
+	var signer *packet.PrivateKey
+	if signed != nil {
+		signer = signed.signingKey().PrivateKey
+		if signer == nil || signer.Encrypted {
+			return nil, errors.InvalidArgumentError("signing key must be decrypted")
+		}
+	}
+
+	// These are the possible ciphers that we'll use for the message.
+	candidateCiphers := []uint8{
+		uint8(packet.CipherAES128),
+		uint8(packet.CipherAES256),
+		uint8(packet.CipherCAST5),
+	}
+	// These are the possible hash functions that we'll use for the signature.
+	candidateHashes := []uint8{
+		hashToHashId(crypto.SHA256),
+		hashToHashId(crypto.SHA512),
+		hashToHashId(crypto.SHA1),
+		hashToHashId(crypto.RIPEMD160),
+	}
+	// In the event that a recipient doesn't specify any supported ciphers
+	// or hash functions, these are the ones that we assume that every
+	// implementation supports.
+	defaultCiphers := candidateCiphers[len(candidateCiphers)-1:]
+	defaultHashes := candidateHashes[len(candidateHashes)-1:]
+
+	encryptKeys := make([]Key, len(to))
+	for i := range to {
+		encryptKeys[i] = to[i].encryptionKey()
+		if encryptKeys[i].PublicKey == nil {
+			return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys")
+		}
+
+		sig := to[i].primaryIdentity().SelfSignature
+
+		preferredSymmetric := sig.PreferredSymmetric
+		if len(preferredSymmetric) == 0 {
+			preferredSymmetric = defaultCiphers
+		}
+		preferredHashes := sig.PreferredHash
+		if len(preferredHashes) == 0 {
+			preferredHashes = defaultHashes
+		}
+		candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
+		candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
+	}
+
+	if len(candidateCiphers) == 0 || len(candidateHashes) == 0 {
+		return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
+	}
+
+	cipher := packet.CipherFunction(candidateCiphers[0])
+	hash, _ := s2k.HashIdToHash(candidateHashes[0])
+	symKey := make([]byte, cipher.KeySize())
+	if _, err := io.ReadFull(rand.Reader, symKey); err != nil {
+		return nil, err
+	}
+
+	for _, key := range encryptKeys {
+		if err := packet.SerializeEncryptedKey(ciphertext, rand.Reader, key.PublicKey, cipher, symKey); err != nil {
+			return nil, err
+		}
+	}
+
+	encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, rand.Reader, cipher, symKey)
+	if err != nil {
+		return
+	}
+
+	if signer != nil {
+		ops := &packet.OnePassSignature{
+			SigType:    packet.SigTypeBinary,
+			Hash:       hash,
+			PubKeyAlgo: signer.PubKeyAlgo,
+			KeyId:      signer.KeyId,
+			IsLast:     true,
+		}
+		if err := ops.Serialize(encryptedData); err != nil {
+			return nil, err
+		}
+	}
+
+	if hints == nil {
+		hints = &FileHints{}
+	}
+
+	w := encryptedData
+	if signer != nil {
+		// If we need to write a signature packet after the literal
+		// data then we need to stop literalData from closing
+		// encryptedData.
+		w = noOpCloser{encryptedData}
+
+	}
+	var epochSeconds uint32
+	if !hints.ModTime.IsZero() {
+		epochSeconds = uint32(hints.ModTime.Unix())
+	}
+	literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds)
+	if err != nil {
+		return nil, err
+	}
+
+	if signer != nil {
+		return signatureWriter{encryptedData, literalData, hash, hash.New(), signer}, nil
+	}
+	return literalData, nil
+}
+
+// signatureWriter hashes the contents of a message while passing it along to
+// literalData. When closed, it closes literalData, writes a signature packet
+// to encryptedData and then also closes encryptedData.
+type signatureWriter struct {
+	encryptedData io.WriteCloser
+	literalData   io.WriteCloser
+	hashType      crypto.Hash
+	h             hash.Hash
+	signer        *packet.PrivateKey
+}
+
+func (s signatureWriter) Write(data []byte) (int, error) {
+	s.h.Write(data)
+	return s.literalData.Write(data)
+}
+
+func (s signatureWriter) Close() error {
+	sig := &packet.Signature{
+		SigType:      packet.SigTypeBinary,
+		PubKeyAlgo:   s.signer.PubKeyAlgo,
+		Hash:         s.hashType,
+		CreationTime: time.Now(),
+		IssuerKeyId:  &s.signer.KeyId,
+	}
+
+	if err := sig.Sign(rand.Reader, s.h, s.signer); err != nil {
+		return err
+	}
+	if err := s.literalData.Close(); err != nil {
+		return err
+	}
+	if err := sig.Serialize(s.encryptedData); err != nil {
+		return err
+	}
+	return s.encryptedData.Close()
+}
+
+// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
+// TODO: we have two of these in OpenPGP packages alone. This probably needs
+// to be promoted somewhere more common.
+type noOpCloser struct {
+	w io.Writer
+}
+
+func (c noOpCloser) Write(data []byte) (n int, err error) {
+	return c.w.Write(data)
+}
+
+func (c noOpCloser) Close() error {
+	return nil
+}
diff --git a/openpgp/write_test.go b/openpgp/write_test.go
new file mode 100644
index 0000000..7df02e7
--- /dev/null
+++ b/openpgp/write_test.go
@@ -0,0 +1,232 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package openpgp
+
+import (
+	"bytes"
+	"crypto/rand"
+	"io"
+	"io/ioutil"
+	"testing"
+	"time"
+)
+
+func TestSignDetached(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSign(out, kring[0], message)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignTextDetached(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex))
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSignText(out, kring[0], message)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId)
+}
+
+func TestSignDetachedDSA(t *testing.T) {
+	kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyPrivateHex))
+	out := bytes.NewBuffer(nil)
+	message := bytes.NewBufferString(signedInput)
+	err := DetachSign(out, kring[0], message)
+	if err != nil {
+		t.Error(err)
+	}
+
+	testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId)
+}
+
+func TestNewEntity(t *testing.T) {
+	if testing.Short() {
+		return
+	}
+
+	e, err := NewEntity(rand.Reader, time.Now(), "Test User", "test", "test@example.com")
+	if err != nil {
+		t.Errorf("failed to create entity: %s", err)
+		return
+	}
+
+	w := bytes.NewBuffer(nil)
+	if err := e.SerializePrivate(w); err != nil {
+		t.Errorf("failed to serialize entity: %s", err)
+		return
+	}
+	serialized := w.Bytes()
+
+	el, err := ReadKeyRing(w)
+	if err != nil {
+		t.Errorf("failed to reparse entity: %s", err)
+		return
+	}
+
+	if len(el) != 1 {
+		t.Errorf("wrong number of entities found, got %d, want 1", len(el))
+	}
+
+	w = bytes.NewBuffer(nil)
+	if err := e.SerializePrivate(w); err != nil {
+		t.Errorf("failed to serialize entity second time: %s", err)
+		return
+	}
+
+	if !bytes.Equal(w.Bytes(), serialized) {
+		t.Errorf("results differed")
+	}
+}
+
+func TestSymmetricEncryption(t *testing.T) {
+	buf := new(bytes.Buffer)
+	plaintext, err := SymmetricallyEncrypt(buf, []byte("testing"), nil)
+	if err != nil {
+		t.Errorf("error writing headers: %s", err)
+		return
+	}
+	message := []byte("hello world\n")
+	_, err = plaintext.Write(message)
+	if err != nil {
+		t.Errorf("error writing to plaintext writer: %s", err)
+	}
+	err = plaintext.Close()
+	if err != nil {
+		t.Errorf("error closing plaintext writer: %s", err)
+	}
+
+	md, err := ReadMessage(buf, nil, func(keys []Key, symmetric bool) ([]byte, error) {
+		return []byte("testing"), nil
+	})
+	if err != nil {
+		t.Errorf("error rereading message: %s", err)
+	}
+	messageBuf := bytes.NewBuffer(nil)
+	_, err = io.Copy(messageBuf, md.UnverifiedBody)
+	if err != nil {
+		t.Errorf("error rereading message: %s", err)
+	}
+	if !bytes.Equal(message, messageBuf.Bytes()) {
+		t.Errorf("recovered message incorrect got '%s', want '%s'", messageBuf.Bytes(), message)
+	}
+}
+
+var testEncryptionTests = []struct {
+	keyRingHex string
+	isSigned   bool
+}{
+	{
+		testKeys1And2PrivateHex,
+		false,
+	},
+	{
+		testKeys1And2PrivateHex,
+		true,
+	},
+	{
+		dsaElGamalTestKeysHex,
+		false,
+	},
+	{
+		dsaElGamalTestKeysHex,
+		true,
+	},
+}
+
+func TestEncryption(t *testing.T) {
+	for i, test := range testEncryptionTests {
+		kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex))
+
+		passphrase := []byte("passphrase")
+		for _, entity := range kring {
+			if entity.PrivateKey != nil && entity.PrivateKey.Encrypted {
+				err := entity.PrivateKey.Decrypt(passphrase)
+				if err != nil {
+					t.Errorf("#%d: failed to decrypt key", i)
+				}
+			}
+			for _, subkey := range entity.Subkeys {
+				if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted {
+					err := subkey.PrivateKey.Decrypt(passphrase)
+					if err != nil {
+						t.Errorf("#%d: failed to decrypt subkey", i)
+					}
+				}
+			}
+		}
+
+		var signed *Entity
+		if test.isSigned {
+			signed = kring[0]
+		}
+
+		buf := new(bytes.Buffer)
+		w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */ )
+		if err != nil {
+			t.Errorf("#%d: error in Encrypt: %s", i, err)
+			continue
+		}
+
+		const message = "testing"
+		_, err = w.Write([]byte(message))
+		if err != nil {
+			t.Errorf("#%d: error writing plaintext: %s", i, err)
+			continue
+		}
+		err = w.Close()
+		if err != nil {
+			t.Errorf("#%d: error closing WriteCloser: %s", i, err)
+			continue
+		}
+
+		md, err := ReadMessage(buf, kring, nil /* no prompt */ )
+		if err != nil {
+			t.Errorf("#%d: error reading message: %s", i, err)
+			continue
+		}
+
+		if test.isSigned {
+			expectedKeyId := kring[0].signingKey().PublicKey.KeyId
+			if md.SignedByKeyId != expectedKeyId {
+				t.Errorf("#%d: message signed by wrong key id, got: %d, want: %d", i, *md.SignedBy, expectedKeyId)
+			}
+			if md.SignedBy == nil {
+				t.Errorf("#%d: failed to find the signing Entity", i)
+			}
+		}
+
+		plaintext, err := ioutil.ReadAll(md.UnverifiedBody)
+		if err != nil {
+			t.Errorf("#%d: error reading encrypted contents: %s", i, err)
+			continue
+		}
+
+		expectedKeyId := kring[0].encryptionKey().PublicKey.KeyId
+		if len(md.EncryptedToKeyIds) != 1 || md.EncryptedToKeyIds[0] != expectedKeyId {
+			t.Errorf("#%d: expected message to be encrypted to %v, but got %#v", i, expectedKeyId, md.EncryptedToKeyIds)
+		}
+
+		if string(plaintext) != message {
+			t.Errorf("#%d: got: %s, want: %s", i, string(plaintext), message)
+		}
+
+		if test.isSigned {
+			if md.SignatureError != nil {
+				t.Errorf("#%d: signature error: %s", i, md.SignatureError)
+			}
+			if md.Signature == nil {
+				t.Error("signature missing")
+			}
+		}
+	}
+}
diff --git a/ripemd160/ripemd160.go b/ripemd160/ripemd160.go
new file mode 100644
index 0000000..da690f0
--- /dev/null
+++ b/ripemd160/ripemd160.go
@@ -0,0 +1,120 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ripemd160 implements the RIPEMD-160 hash algorithm.
+package ripemd160
+
+// RIPEMD-160 is designed by by Hans Dobbertin, Antoon Bosselaers, and Bart
+// Preneel with specifications available at:
+// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf.
+
+import (
+	"crypto"
+	"hash"
+)
+
+func init() {
+	crypto.RegisterHash(crypto.RIPEMD160, New)
+}
+
+// The size of the checksum in bytes.
+const Size = 20
+
+// The block size of the hash algorithm in bytes.
+const BlockSize = 64
+
+const (
+	_s0 = 0x67452301
+	_s1 = 0xefcdab89
+	_s2 = 0x98badcfe
+	_s3 = 0x10325476
+	_s4 = 0xc3d2e1f0
+)
+
+// digest represents the partial evaluation of a checksum.
+type digest struct {
+	s  [5]uint32       // running context
+	x  [BlockSize]byte // temporary buffer
+	nx int             // index into x
+	tc uint64          // total count of bytes processed
+}
+
+func (d *digest) Reset() {
+	d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4
+	d.nx = 0
+	d.tc = 0
+}
+
+// New returns a new hash.Hash computing the checksum.
+func New() hash.Hash {
+	result := new(digest)
+	result.Reset()
+	return result
+}
+
+func (d *digest) Size() int { return Size }
+
+func (d *digest) BlockSize() int { return BlockSize }
+
+func (d *digest) Write(p []byte) (nn int, err error) {
+	nn = len(p)
+	d.tc += uint64(nn)
+	if d.nx > 0 {
+		n := len(p)
+		if n > BlockSize-d.nx {
+			n = BlockSize - d.nx
+		}
+		for i := 0; i < n; i++ {
+			d.x[d.nx+i] = p[i]
+		}
+		d.nx += n
+		if d.nx == BlockSize {
+			_Block(d, d.x[0:])
+			d.nx = 0
+		}
+		p = p[n:]
+	}
+	n := _Block(d, p)
+	p = p[n:]
+	if len(p) > 0 {
+		d.nx = copy(d.x[:], p)
+	}
+	return
+}
+
+func (d0 *digest) Sum(in []byte) []byte {
+	// Make a copy of d0 so that caller can keep writing and summing.
+	d := *d0
+
+	// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.
+	tc := d.tc
+	var tmp [64]byte
+	tmp[0] = 0x80
+	if tc%64 < 56 {
+		d.Write(tmp[0 : 56-tc%64])
+	} else {
+		d.Write(tmp[0 : 64+56-tc%64])
+	}
+
+	// Length in bits.
+	tc <<= 3
+	for i := uint(0); i < 8; i++ {
+		tmp[i] = byte(tc >> (8 * i))
+	}
+	d.Write(tmp[0:8])
+
+	if d.nx != 0 {
+		panic("d.nx != 0")
+	}
+
+	var digest [Size]byte
+	for i, s := range d.s {
+		digest[i*4] = byte(s)
+		digest[i*4+1] = byte(s >> 8)
+		digest[i*4+2] = byte(s >> 16)
+		digest[i*4+3] = byte(s >> 24)
+	}
+
+	return append(in, digest[:]...)
+}
diff --git a/ripemd160/ripemd160_test.go b/ripemd160/ripemd160_test.go
new file mode 100644
index 0000000..5df1b25
--- /dev/null
+++ b/ripemd160/ripemd160_test.go
@@ -0,0 +1,64 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ripemd160
+
+// Test vectors are from:
+// http://homes.esat.kuleuven.be/~bosselae/ripemd160.html
+
+import (
+	"fmt"
+	"io"
+	"testing"
+)
+
+type mdTest struct {
+	out string
+	in  string
+}
+
+var vectors = [...]mdTest{
+	{"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""},
+	{"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"},
+	{"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"},
+	{"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"},
+	{"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"},
+	{"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
+	{"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
+	{"9b752e45573d4b39f4dbd3323cab82bf63326bfb", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
+}
+
+func TestVectors(t *testing.T) {
+	for i := 0; i < len(vectors); i++ {
+		tv := vectors[i]
+		md := New()
+		for j := 0; j < 3; j++ {
+			if j < 2 {
+				io.WriteString(md, tv.in)
+			} else {
+				io.WriteString(md, tv.in[0:len(tv.in)/2])
+				md.Sum(nil)
+				io.WriteString(md, tv.in[len(tv.in)/2:])
+			}
+			s := fmt.Sprintf("%x", md.Sum(nil))
+			if s != tv.out {
+				t.Fatalf("RIPEMD-160[%d](%s) = %s, expected %s", j, tv.in, s, tv.out)
+			}
+			md.Reset()
+		}
+	}
+}
+
+func TestMillionA(t *testing.T) {
+	md := New()
+	for i := 0; i < 100000; i++ {
+		io.WriteString(md, "aaaaaaaaaa")
+	}
+	out := "52783243c1697bdbe16d37f97f68f08325dc1528"
+	s := fmt.Sprintf("%x", md.Sum(nil))
+	if s != out {
+		t.Fatalf("RIPEMD-160 (1 million 'a') = %s, expected %s", s, out)
+	}
+	md.Reset()
+}
diff --git a/ripemd160/ripemd160block.go b/ripemd160/ripemd160block.go
new file mode 100644
index 0000000..7bc8e6c
--- /dev/null
+++ b/ripemd160/ripemd160block.go
@@ -0,0 +1,161 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// RIPEMD-160 block step.
+// In its own file so that a faster assembly or C version
+// can be substituted easily.
+
+package ripemd160
+
+// work buffer indices and roll amounts for one line
+var _n = [80]uint{
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+	7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
+	3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
+	1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
+	4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13,
+}
+
+var _r = [80]uint{
+	11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
+	7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
+	11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
+	11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
+	9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6,
+}
+
+// same for the other parallel one
+var n_ = [80]uint{
+	5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
+	6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
+	15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
+	8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
+	12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11,
+}
+
+var r_ = [80]uint{
+	8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
+	9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
+	9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
+	15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
+	8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11,
+}
+
+func _Block(md *digest, p []byte) int {
+	n := 0
+	var x [16]uint32
+	var alpha, beta uint32
+	for len(p) >= BlockSize {
+		a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4]
+		aa, bb, cc, dd, ee := a, b, c, d, e
+		j := 0
+		for i := 0; i < 16; i++ {
+			x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24
+			j += 4
+		}
+
+		// round 1
+		i := 0
+		for i < 16 {
+			alpha = a + (b ^ c ^ d) + x[_n[i]]
+			s := _r[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + e
+			beta = c<<10 | c>>22
+			a, b, c, d, e = e, alpha, b, beta, d
+
+			// parallel line
+			alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6
+			s = r_[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + ee
+			beta = cc<<10 | cc>>22
+			aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+			i++
+		}
+
+		// round 2
+		for i < 32 {
+			alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999
+			s := _r[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + e
+			beta = c<<10 | c>>22
+			a, b, c, d, e = e, alpha, b, beta, d
+
+			// parallel line
+			alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124
+			s = r_[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + ee
+			beta = cc<<10 | cc>>22
+			aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+			i++
+		}
+
+		// round 3
+		for i < 48 {
+			alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1
+			s := _r[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + e
+			beta = c<<10 | c>>22
+			a, b, c, d, e = e, alpha, b, beta, d
+
+			// parallel line
+			alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3
+			s = r_[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + ee
+			beta = cc<<10 | cc>>22
+			aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+			i++
+		}
+
+		// round 4
+		for i < 64 {
+			alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc
+			s := _r[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + e
+			beta = c<<10 | c>>22
+			a, b, c, d, e = e, alpha, b, beta, d
+
+			// parallel line
+			alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9
+			s = r_[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + ee
+			beta = cc<<10 | cc>>22
+			aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+			i++
+		}
+
+		// round 5
+		for i < 80 {
+			alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e
+			s := _r[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + e
+			beta = c<<10 | c>>22
+			a, b, c, d, e = e, alpha, b, beta, d
+
+			// parallel line
+			alpha = aa + (bb ^ cc ^ dd) + x[n_[i]]
+			s = r_[i]
+			alpha = (alpha<<s | alpha>>(32-s)) + ee
+			beta = cc<<10 | cc>>22
+			aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd
+
+			i++
+		}
+
+		// combine results
+		dd += c + md.s[1]
+		md.s[1] = md.s[2] + d + ee
+		md.s[2] = md.s[3] + e + aa
+		md.s[3] = md.s[4] + a + bb
+		md.s[4] = md.s[0] + b + cc
+		md.s[0] = dd
+
+		p = p[BlockSize:]
+		n += BlockSize
+	}
+	return n
+}
diff --git a/ssh/channel.go b/ssh/channel.go
new file mode 100644
index 0000000..9d75f37
--- /dev/null
+++ b/ssh/channel.go
@@ -0,0 +1,318 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"errors"
+	"io"
+	"sync"
+)
+
+// A Channel is an ordered, reliable, duplex stream that is multiplexed over an
+// SSH connection.
+type Channel interface {
+	// Accept accepts the channel creation request.
+	Accept() error
+	// Reject rejects the channel creation request. After calling this, no
+	// other methods on the Channel may be called. If they are then the
+	// peer is likely to signal a protocol error and drop the connection.
+	Reject(reason RejectionReason, message string) error
+
+	// Read may return a ChannelRequest as an error.
+	Read(data []byte) (int, error)
+	Write(data []byte) (int, error)
+	Close() error
+
+	// AckRequest either sends an ack or nack to the channel request.
+	AckRequest(ok bool) error
+
+	// ChannelType returns the type of the channel, as supplied by the
+	// client.
+	ChannelType() string
+	// ExtraData returns the arbitary payload for this channel, as supplied
+	// by the client. This data is specific to the channel type.
+	ExtraData() []byte
+}
+
+// ChannelRequest represents a request sent on a channel, outside of the normal
+// stream of bytes. It may result from calling Read on a Channel.
+type ChannelRequest struct {
+	Request   string
+	WantReply bool
+	Payload   []byte
+}
+
+func (c ChannelRequest) Error() string {
+	return "channel request received"
+}
+
+// RejectionReason is an enumeration used when rejecting channel creation
+// requests. See RFC 4254, section 5.1.
+type RejectionReason int
+
+const (
+	Prohibited RejectionReason = iota + 1
+	ConnectionFailed
+	UnknownChannelType
+	ResourceShortage
+)
+
+type channel struct {
+	// immutable once created
+	chanType  string
+	extraData []byte
+
+	theyClosed  bool
+	theySentEOF bool
+	weClosed    bool
+	dead        bool
+
+	serverConn            *ServerConn
+	myId, theirId         uint32
+	myWindow, theirWindow uint32
+	maxPacketSize         uint32
+	err                   error
+
+	pendingRequests []ChannelRequest
+	pendingData     []byte
+	head, length    int
+
+	// This lock is inferior to serverConn.lock
+	lock sync.Mutex
+	cond *sync.Cond
+}
+
+func (c *channel) Accept() error {
+	c.serverConn.lock.Lock()
+	defer c.serverConn.lock.Unlock()
+
+	if c.serverConn.err != nil {
+		return c.serverConn.err
+	}
+
+	confirm := channelOpenConfirmMsg{
+		PeersId:       c.theirId,
+		MyId:          c.myId,
+		MyWindow:      c.myWindow,
+		MaxPacketSize: c.maxPacketSize,
+	}
+	return c.serverConn.writePacket(marshal(msgChannelOpenConfirm, confirm))
+}
+
+func (c *channel) Reject(reason RejectionReason, message string) error {
+	c.serverConn.lock.Lock()
+	defer c.serverConn.lock.Unlock()
+
+	if c.serverConn.err != nil {
+		return c.serverConn.err
+	}
+
+	reject := channelOpenFailureMsg{
+		PeersId:  c.theirId,
+		Reason:   uint32(reason),
+		Message:  message,
+		Language: "en",
+	}
+	return c.serverConn.writePacket(marshal(msgChannelOpenFailure, reject))
+}
+
+func (c *channel) handlePacket(packet interface{}) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	switch packet := packet.(type) {
+	case *channelRequestMsg:
+		req := ChannelRequest{
+			Request:   packet.Request,
+			WantReply: packet.WantReply,
+			Payload:   packet.RequestSpecificData,
+		}
+
+		c.pendingRequests = append(c.pendingRequests, req)
+		c.cond.Signal()
+	case *channelCloseMsg:
+		c.theyClosed = true
+		c.cond.Signal()
+	case *channelEOFMsg:
+		c.theySentEOF = true
+		c.cond.Signal()
+	default:
+		panic("unknown packet type")
+	}
+}
+
+func (c *channel) handleData(data []byte) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	// The other side should never send us more than our window.
+	if len(data)+c.length > len(c.pendingData) {
+		// TODO(agl): we should tear down the channel with a protocol
+		// error.
+		return
+	}
+
+	c.myWindow -= uint32(len(data))
+	for i := 0; i < 2; i++ {
+		tail := c.head + c.length
+		if tail > len(c.pendingData) {
+			tail -= len(c.pendingData)
+		}
+		n := copy(c.pendingData[tail:], data)
+		data = data[n:]
+		c.length += n
+	}
+
+	c.cond.Signal()
+}
+
+func (c *channel) Read(data []byte) (n int, err error) {
+	c.lock.Lock()
+	defer c.lock.Unlock()
+
+	if c.err != nil {
+		return 0, c.err
+	}
+
+	if c.myWindow <= uint32(len(c.pendingData))/2 {
+		packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{
+			PeersId:         c.theirId,
+			AdditionalBytes: uint32(len(c.pendingData)) - c.myWindow,
+		})
+		if err := c.serverConn.writePacket(packet); err != nil {
+			return 0, err
+		}
+	}
+
+	for {
+		if c.theySentEOF || c.theyClosed || c.dead {
+			return 0, io.EOF
+		}
+
+		if len(c.pendingRequests) > 0 {
+			req := c.pendingRequests[0]
+			if len(c.pendingRequests) == 1 {
+				c.pendingRequests = nil
+			} else {
+				oldPendingRequests := c.pendingRequests
+				c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1)
+				copy(c.pendingRequests, oldPendingRequests[1:])
+			}
+
+			return 0, req
+		}
+
+		if c.length > 0 {
+			tail := c.head + c.length
+			if tail > len(c.pendingData) {
+				tail -= len(c.pendingData)
+			}
+			n = copy(data, c.pendingData[c.head:tail])
+			c.head += n
+			c.length -= n
+			if c.head == len(c.pendingData) {
+				c.head = 0
+			}
+			return
+		}
+
+		c.cond.Wait()
+	}
+
+	panic("unreachable")
+}
+
+func (c *channel) Write(data []byte) (n int, err error) {
+	for len(data) > 0 {
+		c.lock.Lock()
+		if c.dead || c.weClosed {
+			return 0, io.EOF
+		}
+
+		if c.theirWindow == 0 {
+			c.cond.Wait()
+			continue
+		}
+		c.lock.Unlock()
+
+		todo := data
+		if uint32(len(todo)) > c.theirWindow {
+			todo = todo[:c.theirWindow]
+		}
+
+		packet := make([]byte, 1+4+4+len(todo))
+		packet[0] = msgChannelData
+		packet[1] = byte(c.theirId >> 24)
+		packet[2] = byte(c.theirId >> 16)
+		packet[3] = byte(c.theirId >> 8)
+		packet[4] = byte(c.theirId)
+		packet[5] = byte(len(todo) >> 24)
+		packet[6] = byte(len(todo) >> 16)
+		packet[7] = byte(len(todo) >> 8)
+		packet[8] = byte(len(todo))
+		copy(packet[9:], todo)
+
+		c.serverConn.lock.Lock()
+		if err = c.serverConn.writePacket(packet); err != nil {
+			c.serverConn.lock.Unlock()
+			return
+		}
+		c.serverConn.lock.Unlock()
+
+		n += len(todo)
+		data = data[len(todo):]
+	}
+
+	return
+}
+
+func (c *channel) Close() error {
+	c.serverConn.lock.Lock()
+	defer c.serverConn.lock.Unlock()
+
+	if c.serverConn.err != nil {
+		return c.serverConn.err
+	}
+
+	if c.weClosed {
+		return errors.New("ssh: channel already closed")
+	}
+	c.weClosed = true
+
+	closeMsg := channelCloseMsg{
+		PeersId: c.theirId,
+	}
+	return c.serverConn.writePacket(marshal(msgChannelClose, closeMsg))
+}
+
+func (c *channel) AckRequest(ok bool) error {
+	c.serverConn.lock.Lock()
+	defer c.serverConn.lock.Unlock()
+
+	if c.serverConn.err != nil {
+		return c.serverConn.err
+	}
+
+	if ok {
+		ack := channelRequestSuccessMsg{
+			PeersId: c.theirId,
+		}
+		return c.serverConn.writePacket(marshal(msgChannelSuccess, ack))
+	} else {
+		ack := channelRequestFailureMsg{
+			PeersId: c.theirId,
+		}
+		return c.serverConn.writePacket(marshal(msgChannelFailure, ack))
+	}
+	panic("unreachable")
+}
+
+func (c *channel) ChannelType() string {
+	return c.chanType
+}
+
+func (c *channel) ExtraData() []byte {
+	return c.extraData
+}
diff --git a/ssh/cipher.go b/ssh/cipher.go
new file mode 100644
index 0000000..d91929a
--- /dev/null
+++ b/ssh/cipher.go
@@ -0,0 +1,88 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rc4"
+)
+
+// streamDump is used to dump the initial keystream for stream ciphers. It is a
+// a write-only buffer, and not intended for reading so do not require a mutex.
+var streamDump [512]byte
+
+// noneCipher implements cipher.Stream and provides no encryption. It is used
+// by the transport before the first key-exchange.
+type noneCipher struct{}
+
+func (c noneCipher) XORKeyStream(dst, src []byte) {
+	copy(dst, src)
+}
+
+func newAESCTR(key, iv []byte) (cipher.Stream, error) {
+	c, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	return cipher.NewCTR(c, iv), nil
+}
+
+func newRC4(key, iv []byte) (cipher.Stream, error) {
+	return rc4.NewCipher(key)
+}
+
+type cipherMode struct {
+	keySize  int
+	ivSize   int
+	skip     int
+	createFn func(key, iv []byte) (cipher.Stream, error)
+}
+
+func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
+	if len(key) < c.keySize {
+		panic("ssh: key length too small for cipher")
+	}
+	if len(iv) < c.ivSize {
+		panic("ssh: iv too small for cipher")
+	}
+
+	stream, err := c.createFn(key[:c.keySize], iv[:c.ivSize])
+	if err != nil {
+		return nil, err
+	}
+
+	for remainingToDump := c.skip; remainingToDump > 0; {
+		dumpThisTime := remainingToDump
+		if dumpThisTime > len(streamDump) {
+			dumpThisTime = len(streamDump)
+		}
+		stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
+		remainingToDump -= dumpThisTime
+	}
+
+	return stream, nil
+}
+
+// Specifies a default set of ciphers and a preference order. This is based on
+// OpenSSH's default client preference order, minus algorithms that are not
+// implemented.
+var DefaultCipherOrder = []string{
+	"aes128-ctr", "aes192-ctr", "aes256-ctr",
+	"arcfour256", "arcfour128",
+}
+
+var cipherModes = map[string]*cipherMode{
+	// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
+	// are defined in the order specified in the RFC.
+	"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
+	"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
+	"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
+
+	// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
+	// They are defined in the order specified in the RFC.
+	"arcfour128": {16, 0, 1536, newRC4},
+	"arcfour256": {32, 0, 1536, newRC4},
+}
diff --git a/ssh/cipher_test.go b/ssh/cipher_test.go
new file mode 100644
index 0000000..ea27bd8
--- /dev/null
+++ b/ssh/cipher_test.go
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bytes"
+	"testing"
+)
+
+// TestCipherReversal tests that each cipher factory produces ciphers that can
+// encrypt and decrypt some data successfully.
+func TestCipherReversal(t *testing.T) {
+	testData := []byte("abcdefghijklmnopqrstuvwxyz012345")
+	testKey := []byte("AbCdEfGhIjKlMnOpQrStUvWxYz012345")
+	testIv := []byte("sdflkjhsadflkjhasdflkjhsadfklhsa")
+
+	cryptBuffer := make([]byte, 32)
+
+	for name, cipherMode := range cipherModes {
+		encrypter, err := cipherMode.createCipher(testKey, testIv)
+		if err != nil {
+			t.Errorf("failed to create encrypter for %q: %s", name, err)
+			continue
+		}
+		decrypter, err := cipherMode.createCipher(testKey, testIv)
+		if err != nil {
+			t.Errorf("failed to create decrypter for %q: %s", name, err)
+			continue
+		}
+
+		copy(cryptBuffer, testData)
+
+		encrypter.XORKeyStream(cryptBuffer, cryptBuffer)
+		if name == "none" {
+			if !bytes.Equal(cryptBuffer, testData) {
+				t.Errorf("encryption made change with 'none' cipher")
+				continue
+			}
+		} else {
+			if bytes.Equal(cryptBuffer, testData) {
+				t.Errorf("encryption made no change with %q", name)
+				continue
+			}
+		}
+
+		decrypter.XORKeyStream(cryptBuffer, cryptBuffer)
+		if !bytes.Equal(cryptBuffer, testData) {
+			t.Errorf("decrypted bytes not equal to input with %q", name)
+			continue
+		}
+	}
+}
+
+func TestDefaultCiphersExist(t *testing.T) {
+	for _, cipherAlgo := range DefaultCipherOrder {
+		if _, ok := cipherModes[cipherAlgo]; !ok {
+			t.Errorf("default cipher %q is unknown", cipherAlgo)
+		}
+	}
+}
diff --git a/ssh/client.go b/ssh/client.go
new file mode 100644
index 0000000..eb6c035
--- /dev/null
+++ b/ssh/client.go
@@ -0,0 +1,505 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"crypto"
+	"crypto/rand"
+	"errors"
+	"fmt"
+	"io"
+	"math/big"
+	"net"
+	"sync"
+)
+
+// clientVersion is the fixed identification string that the client will use.
+var clientVersion = []byte("SSH-2.0-Go\r\n")
+
+// ClientConn represents the client side of an SSH connection.
+type ClientConn struct {
+	*transport
+	config *ClientConfig
+	chanlist
+}
+
+// Client returns a new SSH client connection using c as the underlying transport.
+func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
+	conn := &ClientConn{
+		transport: newTransport(c, config.rand()),
+		config:    config,
+	}
+	if err := conn.handshake(); err != nil {
+		conn.Close()
+		return nil, err
+	}
+	go conn.mainLoop()
+	return conn, nil
+}
+
+// handshake performs the client side key exchange. See RFC 4253 Section 7.
+func (c *ClientConn) handshake() error {
+	var magics handshakeMagics
+
+	if _, err := c.Write(clientVersion); err != nil {
+		return err
+	}
+	if err := c.Flush(); err != nil {
+		return err
+	}
+	magics.clientVersion = clientVersion[:len(clientVersion)-2]
+
+	// read remote server version
+	version, err := readVersion(c)
+	if err != nil {
+		return err
+	}
+	magics.serverVersion = version
+	clientKexInit := kexInitMsg{
+		KexAlgos:                supportedKexAlgos,
+		ServerHostKeyAlgos:      supportedHostKeyAlgos,
+		CiphersClientServer:     c.config.Crypto.ciphers(),
+		CiphersServerClient:     c.config.Crypto.ciphers(),
+		MACsClientServer:        supportedMACs,
+		MACsServerClient:        supportedMACs,
+		CompressionClientServer: supportedCompressions,
+		CompressionServerClient: supportedCompressions,
+	}
+	kexInitPacket := marshal(msgKexInit, clientKexInit)
+	magics.clientKexInit = kexInitPacket
+
+	if err := c.writePacket(kexInitPacket); err != nil {
+		return err
+	}
+	packet, err := c.readPacket()
+	if err != nil {
+		return err
+	}
+
+	magics.serverKexInit = packet
+
+	var serverKexInit kexInitMsg
+	if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
+		return err
+	}
+
+	kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(c.transport, &clientKexInit, &serverKexInit)
+	if !ok {
+		return errors.New("ssh: no common algorithms")
+	}
+
+	if serverKexInit.FirstKexFollows && kexAlgo != serverKexInit.KexAlgos[0] {
+		// The server sent a Kex message for the wrong algorithm,
+		// which we have to ignore.
+		if _, err := c.readPacket(); err != nil {
+			return err
+		}
+	}
+
+	var H, K []byte
+	var hashFunc crypto.Hash
+	switch kexAlgo {
+	case kexAlgoDH14SHA1:
+		hashFunc = crypto.SHA1
+		dhGroup14Once.Do(initDHGroup14)
+		H, K, err = c.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
+	default:
+		err = fmt.Errorf("ssh: unexpected key exchange algorithm %v", kexAlgo)
+	}
+	if err != nil {
+		return err
+	}
+
+	if err = c.writePacket([]byte{msgNewKeys}); err != nil {
+		return err
+	}
+	if err = c.transport.writer.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
+		return err
+	}
+	if packet, err = c.readPacket(); err != nil {
+		return err
+	}
+	if packet[0] != msgNewKeys {
+		return UnexpectedMessageError{msgNewKeys, packet[0]}
+	}
+	if err := c.transport.reader.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
+		return err
+	}
+	return c.authenticate(H)
+}
+
+// kexDH performs Diffie-Hellman key agreement on a ClientConn. The
+// returned values are given the same names as in RFC 4253, section 8.
+func (c *ClientConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) ([]byte, []byte, error) {
+	x, err := rand.Int(c.config.rand(), group.p)
+	if err != nil {
+		return nil, nil, err
+	}
+	X := new(big.Int).Exp(group.g, x, group.p)
+	kexDHInit := kexDHInitMsg{
+		X: X,
+	}
+	if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
+		return nil, nil, err
+	}
+
+	packet, err := c.readPacket()
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var kexDHReply = new(kexDHReplyMsg)
+	if err = unmarshal(kexDHReply, packet, msgKexDHReply); err != nil {
+		return nil, nil, err
+	}
+
+	if kexDHReply.Y.Sign() == 0 || kexDHReply.Y.Cmp(group.p) >= 0 {
+		return nil, nil, errors.New("server DH parameter out of bounds")
+	}
+
+	kInt := new(big.Int).Exp(kexDHReply.Y, x, group.p)
+	h := hashFunc.New()
+	writeString(h, magics.clientVersion)
+	writeString(h, magics.serverVersion)
+	writeString(h, magics.clientKexInit)
+	writeString(h, magics.serverKexInit)
+	writeString(h, kexDHReply.HostKey)
+	writeInt(h, X)
+	writeInt(h, kexDHReply.Y)
+	K := make([]byte, intLength(kInt))
+	marshalInt(K, kInt)
+	h.Write(K)
+
+	H := h.Sum(nil)
+
+	return H, K, nil
+}
+
+// mainLoop reads incoming messages and routes channel messages
+// to their respective ClientChans.
+func (c *ClientConn) mainLoop() {
+	// TODO(dfc) signal the underlying close to all channels
+	defer c.Close()
+	for {
+		packet, err := c.readPacket()
+		if err != nil {
+			break
+		}
+		// TODO(dfc) A note on blocking channel use.
+		// The msg, win, data and dataExt channels of a clientChan can
+		// cause this loop to block indefinately if the consumer does
+		// not service them.
+		switch packet[0] {
+		case msgChannelData:
+			if len(packet) < 9 {
+				// malformed data packet
+				break
+			}
+			peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+			if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
+				packet = packet[9:]
+				c.getChan(peersId).stdout.handleData(packet[:length])
+			}
+		case msgChannelExtendedData:
+			if len(packet) < 13 {
+				// malformed data packet
+				break
+			}
+			peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+			datatype := uint32(packet[5])<<24 | uint32(packet[6])<<16 | uint32(packet[7])<<8 | uint32(packet[8])
+			if length := int(packet[9])<<24 | int(packet[10])<<16 | int(packet[11])<<8 | int(packet[12]); length > 0 {
+				packet = packet[13:]
+				// RFC 4254 5.2 defines data_type_code 1 to be data destined
+				// for stderr on interactive sessions. Other data types are
+				// silently discarded.
+				if datatype == 1 {
+					c.getChan(peersId).stderr.handleData(packet[:length])
+				}
+			}
+		default:
+			switch msg := decode(packet).(type) {
+			case *channelOpenMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *channelOpenConfirmMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *channelOpenFailureMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *channelCloseMsg:
+				ch := c.getChan(msg.PeersId)
+				ch.theyClosed = true
+				close(ch.stdin.win)
+				ch.stdout.eof()
+				ch.stderr.eof()
+				close(ch.msg)
+				if !ch.weClosed {
+					ch.weClosed = true
+					ch.sendClose()
+				}
+				c.chanlist.remove(msg.PeersId)
+			case *channelEOFMsg:
+				ch := c.getChan(msg.PeersId)
+				ch.stdout.eof()
+				// RFC 4254 is mute on how EOF affects dataExt messages but
+				// it is logical to signal EOF at the same time.
+				ch.stderr.eof()
+			case *channelRequestSuccessMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *channelRequestFailureMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *channelRequestMsg:
+				c.getChan(msg.PeersId).msg <- msg
+			case *windowAdjustMsg:
+				c.getChan(msg.PeersId).stdin.win <- int(msg.AdditionalBytes)
+			case *disconnectMsg:
+				break
+			default:
+				fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
+			}
+		}
+	}
+}
+
+// Dial connects to the given network address using net.Dial and
+// then initiates a SSH handshake, returning the resulting client connection.
+func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
+	conn, err := net.Dial(network, addr)
+	if err != nil {
+		return nil, err
+	}
+	return Client(conn, config)
+}
+
+// A ClientConfig structure is used to configure a ClientConn. After one has
+// been passed to an SSH function it must not be modified.
+type ClientConfig struct {
+	// Rand provides the source of entropy for key exchange. If Rand is
+	// nil, the cryptographic random reader in package crypto/rand will
+	// be used.
+	Rand io.Reader
+
+	// The username to authenticate.
+	User string
+
+	// A slice of ClientAuth methods. Only the first instance
+	// of a particular RFC 4252 method will be used during authentication.
+	Auth []ClientAuth
+
+	// Cryptographic-related configuration.
+	Crypto CryptoConfig
+}
+
+func (c *ClientConfig) rand() io.Reader {
+	if c.Rand == nil {
+		return rand.Reader
+	}
+	return c.Rand
+}
+
+// A clientChan represents a single RFC 4254 channel that is multiplexed
+// over a single SSH connection.
+type clientChan struct {
+	packetWriter
+	id, peersId uint32
+	stdin       *chanWriter      // receives window adjustments
+	stdout      *chanReader      // receives the payload of channelData messages
+	stderr      *chanReader      // receives the payload of channelExtendedData messages
+	msg         chan interface{} // incoming messages
+	theyClosed  bool             // indicates the close msg has been received from the remote side
+	weClosed    bool             // incidates the close msg has been sent from our side
+}
+
+// newClientChan returns a partially constructed *clientChan
+// using the local id provided. To be usable clientChan.peersId
+// needs to be assigned once known.
+func newClientChan(t *transport, id uint32) *clientChan {
+	c := &clientChan{
+		packetWriter: t,
+		id:           id,
+		msg:          make(chan interface{}, 16),
+	}
+	c.stdin = &chanWriter{
+		win:        make(chan int, 16),
+		clientChan: c,
+	}
+	c.stdout = &chanReader{
+		data:       make(chan []byte, 16),
+		clientChan: c,
+	}
+	c.stderr = &chanReader{
+		data:       make(chan []byte, 16),
+		clientChan: c,
+	}
+	return c
+}
+
+// waitForChannelOpenResponse, if successful, fills out
+// the peerId and records any initial window advertisement.
+func (c *clientChan) waitForChannelOpenResponse() error {
+	switch msg := (<-c.msg).(type) {
+	case *channelOpenConfirmMsg:
+		// fixup peersId field
+		c.peersId = msg.MyId
+		c.stdin.win <- int(msg.MyWindow)
+		return nil
+	case *channelOpenFailureMsg:
+		return errors.New(safeString(msg.Message))
+	}
+	return errors.New("unexpected packet")
+}
+
+// sendEOF sends EOF to the server. RFC 4254 Section 5.3
+func (c *clientChan) sendEOF() error {
+	return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{
+		PeersId: c.peersId,
+	}))
+}
+
+// sendClose signals the intent to close the channel.
+func (c *clientChan) sendClose() error {
+	return c.writePacket(marshal(msgChannelClose, channelCloseMsg{
+		PeersId: c.peersId,
+	}))
+}
+
+// Close closes the channel. This does not close the underlying connection.
+func (c *clientChan) Close() error {
+	if !c.weClosed {
+		c.weClosed = true
+		return c.sendClose()
+	}
+	return nil
+}
+
+// Thread safe channel list.
+type chanlist struct {
+	// protects concurrent access to chans
+	sync.Mutex
+	// chans are indexed by the local id of the channel, clientChan.id.
+	// The PeersId value of messages received by ClientConn.mainLoop is
+	// used to locate the right local clientChan in this slice.
+	chans []*clientChan
+}
+
+// Allocate a new ClientChan with the next avail local id.
+func (c *chanlist) newChan(t *transport) *clientChan {
+	c.Lock()
+	defer c.Unlock()
+	for i := range c.chans {
+		if c.chans[i] == nil {
+			ch := newClientChan(t, uint32(i))
+			c.chans[i] = ch
+			return ch
+		}
+	}
+	i := len(c.chans)
+	ch := newClientChan(t, uint32(i))
+	c.chans = append(c.chans, ch)
+	return ch
+}
+
+func (c *chanlist) getChan(id uint32) *clientChan {
+	c.Lock()
+	defer c.Unlock()
+	return c.chans[int(id)]
+}
+
+func (c *chanlist) remove(id uint32) {
+	c.Lock()
+	defer c.Unlock()
+	c.chans[int(id)] = nil
+}
+
+// A chanWriter represents the stdin of a remote process.
+type chanWriter struct {
+	win        chan int    // receives window adjustments
+	rwin       int         // current rwin size
+	clientChan *clientChan // the channel backing this writer
+}
+
+// Write writes data to the remote process's standard input.
+func (w *chanWriter) Write(data []byte) (written int, err error) {
+	for len(data) > 0 {
+		for w.rwin < 1 {
+			win, ok := <-w.win
+			if !ok {
+				return 0, io.EOF
+			}
+			w.rwin += win
+		}
+		n := min(len(data), w.rwin)
+		peersId := w.clientChan.peersId
+		packet := []byte{
+			msgChannelData,
+			byte(peersId >> 24), byte(peersId >> 16), byte(peersId >> 8), byte(peersId),
+			byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
+		}
+		if err = w.clientChan.writePacket(append(packet, data[:n]...)); err != nil {
+			break
+		}
+		data = data[n:]
+		w.rwin -= n
+		written += n
+	}
+	return
+}
+
+func min(a, b int) int {
+	if a < b {
+		return a
+	}
+	return b
+}
+
+func (w *chanWriter) Close() error {
+	return w.clientChan.sendEOF()
+}
+
+// A chanReader represents stdout or stderr of a remote process.
+type chanReader struct {
+	// TODO(dfc) a fixed size channel may not be the right data structure.
+	// If writes to this channel block, they will block mainLoop, making
+	// it unable to receive new messages from the remote side.
+	data       chan []byte // receives data from remote
+	dataClosed bool        // protects data from being closed twice
+	clientChan *clientChan // the channel backing this reader
+	buf        []byte
+}
+
+// eof signals to the consumer that there is no more data to be received.
+func (r *chanReader) eof() {
+	if !r.dataClosed {
+		r.dataClosed = true
+		close(r.data)
+	}
+}
+
+// handleData sends buf to the reader's consumer. If r.data is closed
+// the data will be silently discarded
+func (r *chanReader) handleData(buf []byte) {
+	if !r.dataClosed {
+		r.data <- buf
+	}
+}
+
+// Read reads data from the remote process's stdout or stderr.
+func (r *chanReader) Read(data []byte) (int, error) {
+	var ok bool
+	for {
+		if len(r.buf) > 0 {
+			n := copy(data, r.buf)
+			r.buf = r.buf[n:]
+			msg := windowAdjustMsg{
+				PeersId:         r.clientChan.peersId,
+				AdditionalBytes: uint32(n),
+			}
+			return n, r.clientChan.writePacket(marshal(msgChannelWindowAdjust, msg))
+		}
+		r.buf, ok = <-r.data
+		if !ok {
+			return 0, io.EOF
+		}
+	}
+	panic("unreachable")
+}
diff --git a/ssh/client_auth.go b/ssh/client_auth.go
new file mode 100644
index 0000000..3a7e9fb
--- /dev/null
+++ b/ssh/client_auth.go
@@ -0,0 +1,316 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"errors"
+	"io"
+)
+
+// authenticate authenticates with the remote server. See RFC 4252.
+func (c *ClientConn) authenticate(session []byte) error {
+	// initiate user auth session
+	if err := c.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
+		return err
+	}
+	packet, err := c.readPacket()
+	if err != nil {
+		return err
+	}
+	var serviceAccept serviceAcceptMsg
+	if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
+		return err
+	}
+	// during the authentication phase the client first attempts the "none" method
+	// then any untried methods suggested by the server.
+	tried, remain := make(map[string]bool), make(map[string]bool)
+	for auth := ClientAuth(new(noneAuth)); auth != nil; {
+		ok, methods, err := auth.auth(session, c.config.User, c.transport, c.config.rand())
+		if err != nil {
+			return err
+		}
+		if ok {
+			// success
+			return nil
+		}
+		tried[auth.method()] = true
+		delete(remain, auth.method())
+		for _, meth := range methods {
+			if tried[meth] {
+				// if we've tried meth already, skip it.
+				continue
+			}
+			remain[meth] = true
+		}
+		auth = nil
+		for _, a := range c.config.Auth {
+			if remain[a.method()] {
+				auth = a
+				break
+			}
+		}
+	}
+	return errors.New("ssh: unable to authenticate, no supported methods remain")
+}
+
+// A ClientAuth represents an instance of an RFC 4252 authentication method.
+type ClientAuth interface {
+	// auth authenticates user over transport t.
+	// Returns true if authentication is successful.
+	// If authentication is not successful, a []string of alternative
+	// method names is returned.
+	auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error)
+
+	// method returns the RFC 4252 method name.
+	method() string
+}
+
+// "none" authentication, RFC 4252 section 5.2.
+type noneAuth int
+
+func (n *noneAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+	if err := t.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
+		User:    user,
+		Service: serviceSSH,
+		Method:  "none",
+	})); err != nil {
+		return false, nil, err
+	}
+
+	return handleAuthResponse(t)
+}
+
+func (n *noneAuth) method() string {
+	return "none"
+}
+
+// "password" authentication, RFC 4252 Section 8.
+type passwordAuth struct {
+	ClientPassword
+}
+
+func (p *passwordAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+	type passwordAuthMsg struct {
+		User     string
+		Service  string
+		Method   string
+		Reply    bool
+		Password string
+	}
+
+	pw, err := p.Password(user)
+	if err != nil {
+		return false, nil, err
+	}
+
+	if err := t.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
+		User:     user,
+		Service:  serviceSSH,
+		Method:   "password",
+		Reply:    false,
+		Password: pw,
+	})); err != nil {
+		return false, nil, err
+	}
+
+	return handleAuthResponse(t)
+}
+
+func (p *passwordAuth) method() string {
+	return "password"
+}
+
+// A ClientPassword implements access to a client's passwords.
+type ClientPassword interface {
+	// Password returns the password to use for user.
+	Password(user string) (password string, err error)
+}
+
+// ClientAuthPassword returns a ClientAuth using password authentication.
+func ClientAuthPassword(impl ClientPassword) ClientAuth {
+	return &passwordAuth{impl}
+}
+
+// ClientKeyring implements access to a client key ring.
+type ClientKeyring interface {
+	// Key returns the i'th rsa.Publickey or dsa.Publickey, or nil if
+	// no key exists at i.
+	Key(i int) (key interface{}, err error)
+
+	// Sign returns a signature of the given data using the i'th key
+	// and the supplied random source.
+	Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
+}
+
+// "publickey" authentication, RFC 4252 Section 7.
+type publickeyAuth struct {
+	ClientKeyring
+}
+
+type publickeyAuthMsg struct {
+	User    string
+	Service string
+	Method  string
+	// HasSig indicates to the reciver packet that the auth request is signed and
+	// should be used for authentication of the request.
+	HasSig   bool
+	Algoname string
+	Pubkey   string
+	// Sig is defined as []byte so marshal will exclude it during validateKey
+	Sig []byte `ssh:"rest"`
+}
+
+func (p *publickeyAuth) auth(session []byte, user string, t *transport, rand io.Reader) (bool, []string, error) {
+
+	// Authentication is performed in two stages. The first stage sends an
+	// enquiry to test if each key is acceptable to the remote. The second
+	// stage attempts to authenticate with the valid keys obtained in the
+	// first stage.
+
+	var index int
+	// a map of public keys to their index in the keyring
+	validKeys := make(map[int]interface{})
+	for {
+		key, err := p.Key(index)
+		if err != nil {
+			return false, nil, err
+		}
+		if key == nil {
+			// no more keys in the keyring
+			break
+		}
+
+		if ok, err := p.validateKey(key, user, t); ok {
+			validKeys[index] = key
+		} else {
+			if err != nil {
+				return false, nil, err
+			}
+		}
+		index++
+	}
+
+	// methods that may continue if this auth is not successful.
+	var methods []string
+	for i, key := range validKeys {
+		pubkey := serializePublickey(key)
+		algoname := algoName(key)
+		sign, err := p.Sign(i, rand, buildDataSignedForAuth(session, userAuthRequestMsg{
+			User:    user,
+			Service: serviceSSH,
+			Method:  p.method(),
+		}, []byte(algoname), pubkey))
+		if err != nil {
+			return false, nil, err
+		}
+		// manually wrap the serialized signature in a string
+		s := serializeSignature(algoname, sign)
+		sig := make([]byte, stringLength(s))
+		marshalString(sig, s)
+		msg := publickeyAuthMsg{
+			User:     user,
+			Service:  serviceSSH,
+			Method:   p.method(),
+			HasSig:   true,
+			Algoname: algoname,
+			Pubkey:   string(pubkey),
+			Sig:      sig,
+		}
+		p := marshal(msgUserAuthRequest, msg)
+		if err := t.writePacket(p); err != nil {
+			return false, nil, err
+		}
+		success, methods, err := handleAuthResponse(t)
+		if err != nil {
+			return false, nil, err
+		}
+		if success {
+			return success, methods, err
+		}
+	}
+	return false, methods, nil
+}
+
+// validateKey validates the key provided it is acceptable to the server.
+func (p *publickeyAuth) validateKey(key interface{}, user string, t *transport) (bool, error) {
+	pubkey := serializePublickey(key)
+	algoname := algoName(key)
+	msg := publickeyAuthMsg{
+		User:     user,
+		Service:  serviceSSH,
+		Method:   p.method(),
+		HasSig:   false,
+		Algoname: algoname,
+		Pubkey:   string(pubkey),
+	}
+	if err := t.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
+		return false, err
+	}
+
+	return p.confirmKeyAck(key, t)
+}
+
+func (p *publickeyAuth) confirmKeyAck(key interface{}, t *transport) (bool, error) {
+	pubkey := serializePublickey(key)
+	algoname := algoName(key)
+
+	for {
+		packet, err := t.readPacket()
+		if err != nil {
+			return false, err
+		}
+		switch packet[0] {
+		case msgUserAuthBanner:
+			// TODO(gpaul): add callback to present the banner to the user
+		case msgUserAuthPubKeyOk:
+			msg := decode(packet).(*userAuthPubKeyOkMsg)
+			if msg.Algo != algoname || msg.PubKey != string(pubkey) {
+				return false, nil
+			}
+			return true, nil
+		case msgUserAuthFailure:
+			return false, nil
+		default:
+			return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
+		}
+	}
+	panic("unreachable")
+}
+
+func (p *publickeyAuth) method() string {
+	return "publickey"
+}
+
+// ClientAuthKeyring returns a ClientAuth using public key authentication.
+func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
+	return &publickeyAuth{impl}
+}
+
+// handleAuthResponse returns whether the preceding authentication request succeeded
+// along with a list of remaining authentication methods to try next and
+// an error if an unexpected response was received.
+func handleAuthResponse(t *transport) (bool, []string, error) {
+	for {
+		packet, err := t.readPacket()
+		if err != nil {
+			return false, nil, err
+		}
+
+		switch packet[0] {
+		case msgUserAuthBanner:
+			// TODO: add callback to present the banner to the user
+		case msgUserAuthFailure:
+			msg := decode(packet).(*userAuthFailureMsg)
+			return false, msg.Methods, nil
+		case msgUserAuthSuccess:
+			return true, nil, nil
+		case msgDisconnect:
+			return false, nil, io.EOF
+		default:
+			return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
+		}
+	}
+	panic("unreachable")
+}
diff --git a/ssh/client_auth_test.go b/ssh/client_auth_test.go
new file mode 100644
index 0000000..c41a93b
--- /dev/null
+++ b/ssh/client_auth_test.go
@@ -0,0 +1,257 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/dsa"
+	"crypto/rsa"
+	_ "crypto/sha1"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"io"
+	"io/ioutil"
+	"math/big"
+	"testing"
+)
+
+// private key for mock server
+const testServerPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA19lGVsTqIT5iiNYRgnoY1CwkbETW5cq+Rzk5v/kTlf31XpSU
+70HVWkbTERECjaYdXM2gGcbb+sxpq6GtXf1M3kVomycqhxwhPv4Cr6Xp4WT/jkFx
+9z+FFzpeodGJWjOH6L2H5uX1Cvr9EDdQp9t9/J32/qBFntY8GwoUI/y/1MSTmMiF
+tupdMODN064vd3gyMKTwrlQ8tZM6aYuyOPsutLlUY7M5x5FwMDYvnPDSeyT/Iw0z
+s3B+NCyqeeMd2T7YzQFnRATj0M7rM5LoSs7DVqVriOEABssFyLj31PboaoLhOKgc
+qoM9khkNzr7FHVvi+DhYM2jD0DwvqZLN6NmnLwIDAQABAoIBAQCGVj+kuSFOV1lT
++IclQYA6bM6uY5mroqcSBNegVxCNhWU03BxlW//BE9tA/+kq53vWylMeN9mpGZea
+riEMIh25KFGWXqXlOOioH8bkMsqA8S7sBmc7jljyv+0toQ9vCCtJ+sueNPhxQQxH
+D2YvUjfzBQ04I9+wn30BByDJ1QA/FoPsunxIOUCcRBE/7jxuLYcpR+JvEF68yYIh
+atXRld4W4in7T65YDR8jK1Uj9XAcNeDYNpT/M6oFLx1aPIlkG86aCWRO19S1jLPT
+b1ZAKHHxPMCVkSYW0RqvIgLXQOR62D0Zne6/2wtzJkk5UCjkSQ2z7ZzJpMkWgDgN
+ifCULFPBAoGBAPoMZ5q1w+zB+knXUD33n1J+niN6TZHJulpf2w5zsW+m2K6Zn62M
+MXndXlVAHtk6p02q9kxHdgov34Uo8VpuNjbS1+abGFTI8NZgFo+bsDxJdItemwC4
+KJ7L1iz39hRN/ZylMRLz5uTYRGddCkeIHhiG2h7zohH/MaYzUacXEEy3AoGBANz8
+e/msleB+iXC0cXKwds26N4hyMdAFE5qAqJXvV3S2W8JZnmU+sS7vPAWMYPlERPk1
+D8Q2eXqdPIkAWBhrx4RxD7rNc5qFNcQWEhCIxC9fccluH1y5g2M+4jpMX2CT8Uv+
+3z+NoJ5uDTXZTnLCfoZzgZ4nCZVZ+6iU5U1+YXFJAoGBANLPpIV920n/nJmmquMj
+orI1R/QXR9Cy56cMC65agezlGOfTYxk5Cfl5Ve+/2IJCfgzwJyjWUsFx7RviEeGw
+64o7JoUom1HX+5xxdHPsyZ96OoTJ5RqtKKoApnhRMamau0fWydH1yeOEJd+TRHhc
+XStGfhz8QNa1dVFvENczja1vAoGABGWhsd4VPVpHMc7lUvrf4kgKQtTC2PjA4xoc
+QJ96hf/642sVE76jl+N6tkGMzGjnVm4P2j+bOy1VvwQavKGoXqJBRd5Apppv727g
+/SM7hBXKFc/zH80xKBBgP/i1DR7kdjakCoeu4ngeGywvu2jTS6mQsqzkK+yWbUxJ
+I7mYBsECgYB/KNXlTEpXtz/kwWCHFSYA8U74l7zZbVD8ul0e56JDK+lLcJ0tJffk
+gqnBycHj6AhEycjda75cs+0zybZvN4x65KZHOGW/O/7OAWEcZP5TPb3zf9ned3Hl
+NsZoFj52ponUM6+99A2CmezFCN16c4mbA//luWF+k3VVqR6BpkrhKw==
+-----END RSA PRIVATE KEY-----`
+
+const testClientPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld
+r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ
+tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC
+nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW
+2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB
+y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr
+rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg==
+-----END RSA PRIVATE KEY-----`
+
+// keychain implements the ClientPublickey interface
+type keychain struct {
+	keys []interface{}
+}
+
+func (k *keychain) Key(i int) (interface{}, error) {
+	if i < 0 || i >= len(k.keys) {
+		return nil, nil
+	}
+	switch key := k.keys[i].(type) {
+	case *rsa.PrivateKey:
+		return key.PublicKey, nil
+	case *dsa.PrivateKey:
+		return key.PublicKey, nil
+	}
+	panic("unknown key type")
+}
+
+func (k *keychain) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
+	hashFunc := crypto.SHA1
+	h := hashFunc.New()
+	h.Write(data)
+	digest := h.Sum(nil)
+	switch key := k.keys[i].(type) {
+	case *rsa.PrivateKey:
+		return rsa.SignPKCS1v15(rand, key, hashFunc, digest)
+	}
+	return nil, errors.New("unknown key type")
+}
+
+func (k *keychain) loadPEM(file string) error {
+	buf, err := ioutil.ReadFile(file)
+	if err != nil {
+		return err
+	}
+	block, _ := pem.Decode(buf)
+	if block == nil {
+		return errors.New("ssh: no key found")
+	}
+	r, err := x509.ParsePKCS1PrivateKey(block.Bytes)
+	if err != nil {
+		return err
+	}
+	k.keys = append(k.keys, r)
+	return nil
+}
+
+// password implements the ClientPassword interface
+type password string
+
+func (p password) Password(user string) (string, error) {
+	return string(p), nil
+}
+
+// reused internally by tests
+var (
+	rsakey         *rsa.PrivateKey
+	dsakey         *dsa.PrivateKey
+	clientKeychain = new(keychain)
+	clientPassword = password("tiger")
+	serverConfig   = &ServerConfig{
+		PasswordCallback: func(user, pass string) bool {
+			return user == "testuser" && pass == string(clientPassword)
+		},
+		PublicKeyCallback: func(user, algo string, pubkey []byte) bool {
+			key := clientKeychain.keys[0].(*rsa.PrivateKey).PublicKey
+			expected := []byte(serializePublickey(key))
+			algoname := algoName(key)
+			return user == "testuser" && algo == algoname && bytes.Equal(pubkey, expected)
+		},
+	}
+)
+
+func init() {
+	if err := serverConfig.SetRSAPrivateKey([]byte(testServerPrivateKey)); err != nil {
+		panic("unable to set private key: " + err.Error())
+	}
+
+	block, _ := pem.Decode([]byte(testClientPrivateKey))
+	rsakey, _ = x509.ParsePKCS1PrivateKey(block.Bytes)
+
+	clientKeychain.keys = append(clientKeychain.keys, rsakey)
+	dsakey = new(dsa.PrivateKey)
+	// taken from crypto/dsa/dsa_test.go
+	dsakey.P, _ = new(big.Int).SetString("A9B5B793FB4785793D246BAE77E8FF63CA52F442DA763C440259919FE1BC1D6065A9350637A04F75A2F039401D49F08E066C4D275A5A65DA5684BC563C14289D7AB8A67163BFBF79D85972619AD2CFF55AB0EE77A9002B0EF96293BDD0F42685EBB2C66C327079F6C98000FBCB79AACDE1BC6F9D5C7B1A97E3D9D54ED7951FEF", 16)
+	dsakey.Q, _ = new(big.Int).SetString("E1D3391245933D68A0714ED34BBCB7A1F422B9C1", 16)
+	dsakey.G, _ = new(big.Int).SetString("634364FC25248933D01D1993ECABD0657CC0CB2CEED7ED2E3E8AECDFCDC4A25C3B15E9E3B163ACA2984B5539181F3EFF1A5E8903D71D5B95DA4F27202B77D2C44B430BB53741A8D59A8F86887525C9F2A6A5980A195EAA7F2FF910064301DEF89D3AA213E1FAC7768D89365318E370AF54A112EFBA9246D9158386BA1B4EEFDA", 16)
+	dsakey.Y, _ = new(big.Int).SetString("32969E5780CFE1C849A1C276D7AEB4F38A23B591739AA2FE197349AEEBD31366AEE5EB7E6C6DDB7C57D02432B30DB5AA66D9884299FAA72568944E4EEDC92EA3FBC6F39F53412FBCC563208F7C15B737AC8910DBC2D9C9B8C001E72FDC40EB694AB1F06A5A2DBD18D9E36C66F31F566742F11EC0A52E9F7B89355C02FB5D32D2", 16)
+	dsakey.X, _ = new(big.Int).SetString("5078D4D29795CBE76D3AACFE48C9AF0BCDBEE91A", 16)
+}
+
+// newMockAuthServer creates a new Server bound to 
+// the loopback interface. The server exits after 
+// processing one handshake.
+func newMockAuthServer(t *testing.T) string {
+	l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
+	if err != nil {
+		t.Fatalf("unable to newMockAuthServer: %s", err)
+	}
+	go func() {
+		defer l.Close()
+		c, err := l.Accept()
+		defer c.Close()
+		if err != nil {
+			t.Errorf("Unable to accept incoming connection: %v", err)
+			return
+		}
+		if err := c.Handshake(); err != nil {
+			// not Errorf because this is expected to
+			// fail for some tests.
+			t.Logf("Handshaking error: %v", err)
+			return
+		}
+	}()
+	return l.Addr().String()
+}
+
+func TestClientAuthPublickey(t *testing.T) {
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthKeyring(clientKeychain),
+		},
+	}
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err != nil {
+		t.Fatalf("unable to dial remote side: %s", err)
+	}
+	c.Close()
+}
+
+func TestClientAuthPassword(t *testing.T) {
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthPassword(clientPassword),
+		},
+	}
+
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err != nil {
+		t.Fatalf("unable to dial remote side: %s", err)
+	}
+	c.Close()
+}
+
+func TestClientAuthWrongPassword(t *testing.T) {
+	wrongPw := password("wrong")
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthPassword(wrongPw),
+			ClientAuthKeyring(clientKeychain),
+		},
+	}
+
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err != nil {
+		t.Fatalf("unable to dial remote side: %s", err)
+	}
+	c.Close()
+}
+
+// the mock server will only authenticate ssh-rsa keys
+func TestClientAuthInvalidPublickey(t *testing.T) {
+	kc := new(keychain)
+	kc.keys = append(kc.keys, dsakey)
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthKeyring(kc),
+		},
+	}
+
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err == nil {
+		c.Close()
+		t.Fatalf("dsa private key should not have authenticated with rsa public key")
+	}
+}
+
+// the client should authenticate with the second key
+func TestClientAuthRSAandDSA(t *testing.T) {
+	kc := new(keychain)
+	kc.keys = append(kc.keys, dsakey, rsakey)
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthKeyring(kc),
+		},
+	}
+	c, err := Dial("tcp", newMockAuthServer(t), config)
+	if err != nil {
+		t.Fatalf("client could not authenticate with rsa key: %v", err)
+	}
+	c.Close()
+}
diff --git a/ssh/client_func_test.go b/ssh/client_func_test.go
new file mode 100644
index 0000000..b4bdba9
--- /dev/null
+++ b/ssh/client_func_test.go
@@ -0,0 +1,61 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// ClientConn functional tests.
+// These tests require a running ssh server listening on port 22
+// on the local host. Functional tests will be skipped unless
+// -ssh.user and -ssh.pass must be passed to gotest.
+
+import (
+	"flag"
+	"testing"
+)
+
+var (
+	sshuser    = flag.String("ssh.user", "", "ssh username")
+	sshpass    = flag.String("ssh.pass", "", "ssh password")
+	sshprivkey = flag.String("ssh.privkey", "", "ssh privkey file")
+)
+
+func TestFuncPasswordAuth(t *testing.T) {
+	if *sshuser == "" {
+		t.Log("ssh.user not defined, skipping test")
+		return
+	}
+	config := &ClientConfig{
+		User: *sshuser,
+		Auth: []ClientAuth{
+			ClientAuthPassword(password(*sshpass)),
+		},
+	}
+	conn, err := Dial("tcp", "localhost:22", config)
+	if err != nil {
+		t.Fatalf("Unable to connect: %s", err)
+	}
+	defer conn.Close()
+}
+
+func TestFuncPublickeyAuth(t *testing.T) {
+	if *sshuser == "" {
+		t.Log("ssh.user not defined, skipping test")
+		return
+	}
+	kc := new(keychain)
+	if err := kc.loadPEM(*sshprivkey); err != nil {
+		t.Fatalf("unable to load private key: %s", err)
+	}
+	config := &ClientConfig{
+		User: *sshuser,
+		Auth: []ClientAuth{
+			ClientAuthKeyring(kc),
+		},
+	}
+	conn, err := Dial("tcp", "localhost:22", config)
+	if err != nil {
+		t.Fatalf("unable to connect: %s", err)
+	}
+	defer conn.Close()
+}
diff --git a/ssh/common.go b/ssh/common.go
new file mode 100644
index 0000000..6844fb8
--- /dev/null
+++ b/ssh/common.go
@@ -0,0 +1,239 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"crypto/dsa"
+	"crypto/rsa"
+	"math/big"
+	"strconv"
+	"sync"
+)
+
+// These are string constants in the SSH protocol.
+const (
+	kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
+	hostAlgoRSA     = "ssh-rsa"
+	macSHA196       = "hmac-sha1-96"
+	compressionNone = "none"
+	serviceUserAuth = "ssh-userauth"
+	serviceSSH      = "ssh-connection"
+)
+
+var supportedKexAlgos = []string{kexAlgoDH14SHA1}
+var supportedHostKeyAlgos = []string{hostAlgoRSA}
+var supportedMACs = []string{macSHA196}
+var supportedCompressions = []string{compressionNone}
+
+// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
+type dhGroup struct {
+	g, p *big.Int
+}
+
+// dhGroup14 is the group called diffie-hellman-group14-sha1 in RFC 4253 and
+// Oakley Group 14 in RFC 3526.
+var dhGroup14 *dhGroup
+
+var dhGroup14Once sync.Once
+
+func initDHGroup14() {
+	p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
+
+	dhGroup14 = &dhGroup{
+		g: new(big.Int).SetInt64(2),
+		p: p,
+	}
+}
+
+// UnexpectedMessageError results when the SSH message that we received didn't
+// match what we wanted.
+type UnexpectedMessageError struct {
+	expected, got uint8
+}
+
+func (u UnexpectedMessageError) Error() string {
+	return "ssh: unexpected message type " + strconv.Itoa(int(u.got)) + " (expected " + strconv.Itoa(int(u.expected)) + ")"
+}
+
+// ParseError results from a malformed SSH message.
+type ParseError struct {
+	msgType uint8
+}
+
+func (p ParseError) Error() string {
+	return "ssh: parse error in message type " + strconv.Itoa(int(p.msgType))
+}
+
+type handshakeMagics struct {
+	clientVersion, serverVersion []byte
+	clientKexInit, serverKexInit []byte
+}
+
+func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
+	for _, clientAlgo := range clientAlgos {
+		for _, serverAlgo := range serverAlgos {
+			if clientAlgo == serverAlgo {
+				return clientAlgo, true
+			}
+		}
+	}
+
+	return
+}
+
+func findAgreedAlgorithms(transport *transport, clientKexInit, serverKexInit *kexInitMsg) (kexAlgo, hostKeyAlgo string, ok bool) {
+	kexAlgo, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
+	if !ok {
+		return
+	}
+
+	hostKeyAlgo, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
+	if !ok {
+		return
+	}
+
+	transport.writer.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
+	if !ok {
+		return
+	}
+
+	transport.reader.cipherAlgo, ok = findCommonAlgorithm(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
+	if !ok {
+		return
+	}
+
+	transport.writer.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
+	if !ok {
+		return
+	}
+
+	transport.reader.macAlgo, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
+	if !ok {
+		return
+	}
+
+	transport.writer.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
+	if !ok {
+		return
+	}
+
+	transport.reader.compressionAlgo, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
+	if !ok {
+		return
+	}
+
+	ok = true
+	return
+}
+
+// Cryptographic configuration common to both ServerConfig and ClientConfig.
+type CryptoConfig struct {
+	// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
+	// used.
+	Ciphers []string
+}
+
+func (c *CryptoConfig) ciphers() []string {
+	if c.Ciphers == nil {
+		return DefaultCipherOrder
+	}
+	return c.Ciphers
+}
+
+// serialize a signed slice according to RFC 4254 6.6.
+func serializeSignature(algoname string, sig []byte) []byte {
+	length := stringLength([]byte(algoname))
+	length += stringLength(sig)
+
+	ret := make([]byte, length)
+	r := marshalString(ret, []byte(algoname))
+	r = marshalString(r, sig)
+
+	return ret
+}
+
+// serialize an rsa.PublicKey or dsa.PublicKey according to RFC 4253 6.6.
+func serializePublickey(key interface{}) []byte {
+	algoname := algoName(key)
+	switch key := key.(type) {
+	case rsa.PublicKey:
+		e := new(big.Int).SetInt64(int64(key.E))
+		length := stringLength([]byte(algoname))
+		length += intLength(e)
+		length += intLength(key.N)
+		ret := make([]byte, length)
+		r := marshalString(ret, []byte(algoname))
+		r = marshalInt(r, e)
+		marshalInt(r, key.N)
+		return ret
+	case dsa.PublicKey:
+		length := stringLength([]byte(algoname))
+		length += intLength(key.P)
+		length += intLength(key.Q)
+		length += intLength(key.G)
+		length += intLength(key.Y)
+		ret := make([]byte, length)
+		r := marshalString(ret, []byte(algoname))
+		r = marshalInt(r, key.P)
+		r = marshalInt(r, key.Q)
+		r = marshalInt(r, key.G)
+		marshalInt(r, key.Y)
+		return ret
+	}
+	panic("unexpected key type")
+}
+
+func algoName(key interface{}) string {
+	switch key.(type) {
+	case rsa.PublicKey:
+		return "ssh-rsa"
+	case dsa.PublicKey:
+		return "ssh-dss"
+	}
+	panic("unexpected key type")
+}
+
+// buildDataSignedForAuth returns the data that is signed in order to prove
+// posession of a private key. See RFC 4252, section 7.
+func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
+	user := []byte(req.User)
+	service := []byte(req.Service)
+	method := []byte(req.Method)
+
+	length := stringLength(sessionId)
+	length += 1
+	length += stringLength(user)
+	length += stringLength(service)
+	length += stringLength(method)
+	length += 1
+	length += stringLength(algo)
+	length += stringLength(pubKey)
+
+	ret := make([]byte, length)
+	r := marshalString(ret, sessionId)
+	r[0] = msgUserAuthRequest
+	r = r[1:]
+	r = marshalString(r, user)
+	r = marshalString(r, service)
+	r = marshalString(r, method)
+	r[0] = 1
+	r = r[1:]
+	r = marshalString(r, algo)
+	r = marshalString(r, pubKey)
+	return ret
+}
+
+// safeString sanitises s according to RFC 4251, section 9.2. 
+// All control characters except tab, carriage return and newline are
+// replaced by 0x20.
+func safeString(s string) string {
+	out := []byte(s)
+	for i, c := range out {
+		if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
+			out[i] = 0x20
+		}
+	}
+	return string(out)
+}
diff --git a/ssh/common_test.go b/ssh/common_test.go
new file mode 100644
index 0000000..058fb04
--- /dev/null
+++ b/ssh/common_test.go
@@ -0,0 +1,26 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"testing"
+)
+
+func TestSafeString(t *testing.T) {
+	strings := map[string]string{
+		"\x20\x0d\x0a":  "\x20\x0d\x0a",
+		"flibble":       "flibble",
+		"new\x20line":   "new\x20line",
+		"123456\x07789": "123456 789",
+		"\t\t\x10\r\n":  "\t\t \r\n",
+	}
+
+	for s, expected := range strings {
+		actual := safeString(s)
+		if expected != actual {
+			t.Errorf("expected: %v, actual: %v", []byte(expected), []byte(actual))
+		}
+	}
+}
diff --git a/ssh/doc.go b/ssh/doc.go
new file mode 100644
index 0000000..e7deb5e
--- /dev/null
+++ b/ssh/doc.go
@@ -0,0 +1,127 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package ssh implements an SSH client and server.
+
+SSH is a transport security protocol, an authentication protocol and a
+family of application protocols. The most typical application level
+protocol is a remote shell and this is specifically implemented.  However,
+the multiplexed nature of SSH is exposed to users that wish to support
+others.
+
+An SSH server is represented by a ServerConfig, which holds certificate
+details and handles authentication of ServerConns.
+
+	config := new(ssh.ServerConfig)
+	config.PubKeyCallback = pubKeyAuth
+	config.PasswordCallback = passwordAuth
+
+	pemBytes, err := ioutil.ReadFile("id_rsa")
+	if err != nil {
+		panic("Failed to load private key")
+	}
+	err = config.SetRSAPrivateKey(pemBytes)
+	if err != nil {
+		panic("Failed to parse private key")
+	}
+
+Once a ServerConfig has been configured, connections can be accepted.
+
+	listener := Listen("tcp", "0.0.0.0:2022", config)
+	sConn, err := listener.Accept()
+	if err != nil {
+		panic("failed to accept incoming connection")
+	}
+	if err := sConn.Handshake(conn); err != nil {
+		panic("failed to handshake")
+	}
+
+An SSH connection multiplexes several channels, which must be accepted themselves:
+
+	for {
+		channel, err := sConn.Accept()
+		if err != nil {
+			panic("error from Accept")
+		}
+
+		...
+	}
+
+Accept reads from the connection, demultiplexes packets to their corresponding
+channels and returns when a new channel request is seen. Some goroutine must
+always be calling Accept; otherwise no messages will be forwarded to the
+channels.
+
+Channels have a type, depending on the application level protocol intended. In
+the case of a shell, the type is "session" and ServerShell may be used to
+present a simple terminal interface.
+
+	if channel.ChannelType() != "session" {
+		channel.Reject(UnknownChannelType, "unknown channel type")
+		return
+	}
+	channel.Accept()
+
+	term := terminal.NewTerminal(channel, "> ")
+	serverTerm := &ssh.ServerTerminal{
+		Term: term,
+		Channel: channel,
+	}
+	go func() {
+		defer channel.Close()
+		for {
+			line, err := serverTerm.ReadLine()
+			if err != nil {
+				break
+			}
+			println(line)
+		}
+		return
+	}()
+
+To authenticate with the remote server you must pass at least one implementation of 
+ClientAuth via the Auth field in ClientConfig.
+
+	// password implements the ClientPassword interface
+	type password string
+
+	func (p password) Password(user string) (string, error) {
+		return string(p), nil
+	}
+
+	config := &ssh.ClientConfig {
+		User: "username",
+		Auth: []ClientAuth {
+			// ClientAuthPassword wraps a ClientPassword implementation
+			// in a type that implements ClientAuth.
+			ClientAuthPassword(password("yourpassword")),
+		}
+	}
+
+An SSH client is represented with a ClientConn. Currently only the "password"
+authentication method is supported.
+
+	config := &ClientConfig{
+		User: "username",
+		Auth: []ClientAuth{ ... },
+	}
+	client, err := Dial("yourserver.com:22", config)
+
+Each ClientConn can support multiple interactive sessions, represented by a Session.
+
+	session, err := client.NewSession()
+
+Once a Session is created, you can execute a single command on the remote side
+using the Exec method.
+
+	b := bytes.NewBuffer()
+	session.Stdin = b
+	if err := session.Run("/usr/bin/whoami"); err != nil {
+		panic("Failed to exec: " + err.String())
+	}
+	fmt.Println(bytes.String())
+	session.Close()
+*/
+package ssh
diff --git a/ssh/messages.go b/ssh/messages.go
new file mode 100644
index 0000000..34ad131
--- /dev/null
+++ b/ssh/messages.go
@@ -0,0 +1,640 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bytes"
+	"io"
+	"math/big"
+	"reflect"
+)
+
+// These are SSH message type numbers. They are scattered around several
+// documents but many were taken from
+// http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
+const (
+	msgDisconnect     = 1
+	msgIgnore         = 2
+	msgUnimplemented  = 3
+	msgDebug          = 4
+	msgServiceRequest = 5
+	msgServiceAccept  = 6
+
+	msgKexInit = 20
+	msgNewKeys = 21
+
+	msgKexDHInit  = 30
+	msgKexDHReply = 31
+
+	msgUserAuthRequest  = 50
+	msgUserAuthFailure  = 51
+	msgUserAuthSuccess  = 52
+	msgUserAuthBanner   = 53
+	msgUserAuthPubKeyOk = 60
+
+	msgGlobalRequest  = 80
+	msgRequestSuccess = 81
+	msgRequestFailure = 82
+
+	msgChannelOpen         = 90
+	msgChannelOpenConfirm  = 91
+	msgChannelOpenFailure  = 92
+	msgChannelWindowAdjust = 93
+	msgChannelData         = 94
+	msgChannelExtendedData = 95
+	msgChannelEOF          = 96
+	msgChannelClose        = 97
+	msgChannelRequest      = 98
+	msgChannelSuccess      = 99
+	msgChannelFailure      = 100
+)
+
+// SSH messages:
+//
+// These structures mirror the wire format of the corresponding SSH messages.
+// They are marshaled using reflection with the marshal and unmarshal functions
+// in this file. The only wrinkle is that a final member of type []byte with a
+// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
+
+// See RFC 4253, section 11.1.
+type disconnectMsg struct {
+	Reason   uint32
+	Message  string
+	Language string
+}
+
+// See RFC 4253, section 7.1.
+type kexInitMsg struct {
+	Cookie                  [16]byte
+	KexAlgos                []string
+	ServerHostKeyAlgos      []string
+	CiphersClientServer     []string
+	CiphersServerClient     []string
+	MACsClientServer        []string
+	MACsServerClient        []string
+	CompressionClientServer []string
+	CompressionServerClient []string
+	LanguagesClientServer   []string
+	LanguagesServerClient   []string
+	FirstKexFollows         bool
+	Reserved                uint32
+}
+
+// See RFC 4253, section 8.
+type kexDHInitMsg struct {
+	X *big.Int
+}
+
+type kexDHReplyMsg struct {
+	HostKey   []byte
+	Y         *big.Int
+	Signature []byte
+}
+
+// See RFC 4253, section 10.
+type serviceRequestMsg struct {
+	Service string
+}
+
+// See RFC 4253, section 10.
+type serviceAcceptMsg struct {
+	Service string
+}
+
+// See RFC 4252, section 5.
+type userAuthRequestMsg struct {
+	User    string
+	Service string
+	Method  string
+	Payload []byte `ssh:"rest"`
+}
+
+// See RFC 4252, section 5.1
+type userAuthFailureMsg struct {
+	Methods        []string
+	PartialSuccess bool
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenMsg struct {
+	ChanType         string
+	PeersId          uint32
+	PeersWindow      uint32
+	MaxPacketSize    uint32
+	TypeSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenConfirmMsg struct {
+	PeersId          uint32
+	MyId             uint32
+	MyWindow         uint32
+	MaxPacketSize    uint32
+	TypeSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.1.
+type channelOpenFailureMsg struct {
+	PeersId  uint32
+	Reason   uint32
+	Message  string
+	Language string
+}
+
+type channelRequestMsg struct {
+	PeersId             uint32
+	Request             string
+	WantReply           bool
+	RequestSpecificData []byte `ssh:"rest"`
+}
+
+// See RFC 4254, section 5.4.
+type channelRequestSuccessMsg struct {
+	PeersId uint32
+}
+
+// See RFC 4254, section 5.4.
+type channelRequestFailureMsg struct {
+	PeersId uint32
+}
+
+// See RFC 4254, section 5.3
+type channelCloseMsg struct {
+	PeersId uint32
+}
+
+// See RFC 4254, section 5.3
+type channelEOFMsg struct {
+	PeersId uint32
+}
+
+// See RFC 4254, section 4
+type globalRequestMsg struct {
+	Type      string
+	WantReply bool
+}
+
+// See RFC 4254, section 5.2
+type windowAdjustMsg struct {
+	PeersId         uint32
+	AdditionalBytes uint32
+}
+
+// See RFC 4252, section 7
+type userAuthPubKeyOkMsg struct {
+	Algo   string
+	PubKey string
+}
+
+// unmarshal parses the SSH wire data in packet into out using reflection.
+// expectedType is the expected SSH message type. It either returns nil on
+// success, or a ParseError or UnexpectedMessageError on error.
+func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
+	if len(packet) == 0 {
+		return ParseError{expectedType}
+	}
+	if packet[0] != expectedType {
+		return UnexpectedMessageError{expectedType, packet[0]}
+	}
+	packet = packet[1:]
+
+	v := reflect.ValueOf(out).Elem()
+	structType := v.Type()
+	var ok bool
+	for i := 0; i < v.NumField(); i++ {
+		field := v.Field(i)
+		t := field.Type()
+		switch t.Kind() {
+		case reflect.Bool:
+			if len(packet) < 1 {
+				return ParseError{expectedType}
+			}
+			field.SetBool(packet[0] != 0)
+			packet = packet[1:]
+		case reflect.Array:
+			if t.Elem().Kind() != reflect.Uint8 {
+				panic("array of non-uint8")
+			}
+			if len(packet) < t.Len() {
+				return ParseError{expectedType}
+			}
+			for j := 0; j < t.Len(); j++ {
+				field.Index(j).Set(reflect.ValueOf(packet[j]))
+			}
+			packet = packet[t.Len():]
+		case reflect.Uint32:
+			var u32 uint32
+			if u32, packet, ok = parseUint32(packet); !ok {
+				return ParseError{expectedType}
+			}
+			field.SetUint(uint64(u32))
+		case reflect.String:
+			var s []byte
+			if s, packet, ok = parseString(packet); !ok {
+				return ParseError{expectedType}
+			}
+			field.SetString(string(s))
+		case reflect.Slice:
+			switch t.Elem().Kind() {
+			case reflect.Uint8:
+				if structType.Field(i).Tag.Get("ssh") == "rest" {
+					field.Set(reflect.ValueOf(packet))
+					packet = nil
+				} else {
+					var s []byte
+					if s, packet, ok = parseString(packet); !ok {
+						return ParseError{expectedType}
+					}
+					field.Set(reflect.ValueOf(s))
+				}
+			case reflect.String:
+				var nl []string
+				if nl, packet, ok = parseNameList(packet); !ok {
+					return ParseError{expectedType}
+				}
+				field.Set(reflect.ValueOf(nl))
+			default:
+				panic("slice of unknown type")
+			}
+		case reflect.Ptr:
+			if t == bigIntType {
+				var n *big.Int
+				if n, packet, ok = parseInt(packet); !ok {
+					return ParseError{expectedType}
+				}
+				field.Set(reflect.ValueOf(n))
+			} else {
+				panic("pointer to unknown type")
+			}
+		default:
+			panic("unknown type")
+		}
+	}
+
+	if len(packet) != 0 {
+		return ParseError{expectedType}
+	}
+
+	return nil
+}
+
+// marshal serializes the message in msg, using the given message type.
+func marshal(msgType uint8, msg interface{}) []byte {
+	var out []byte
+	out = append(out, msgType)
+
+	v := reflect.ValueOf(msg)
+	structType := v.Type()
+	for i := 0; i < v.NumField(); i++ {
+		field := v.Field(i)
+		t := field.Type()
+		switch t.Kind() {
+		case reflect.Bool:
+			var v uint8
+			if field.Bool() {
+				v = 1
+			}
+			out = append(out, v)
+		case reflect.Array:
+			if t.Elem().Kind() != reflect.Uint8 {
+				panic("array of non-uint8")
+			}
+			for j := 0; j < t.Len(); j++ {
+				out = append(out, byte(field.Index(j).Uint()))
+			}
+		case reflect.Uint32:
+			u32 := uint32(field.Uint())
+			out = append(out, byte(u32>>24))
+			out = append(out, byte(u32>>16))
+			out = append(out, byte(u32>>8))
+			out = append(out, byte(u32))
+		case reflect.String:
+			s := field.String()
+			out = append(out, byte(len(s)>>24))
+			out = append(out, byte(len(s)>>16))
+			out = append(out, byte(len(s)>>8))
+			out = append(out, byte(len(s)))
+			out = append(out, s...)
+		case reflect.Slice:
+			switch t.Elem().Kind() {
+			case reflect.Uint8:
+				length := field.Len()
+				if structType.Field(i).Tag.Get("ssh") != "rest" {
+					out = append(out, byte(length>>24))
+					out = append(out, byte(length>>16))
+					out = append(out, byte(length>>8))
+					out = append(out, byte(length))
+				}
+				for j := 0; j < length; j++ {
+					out = append(out, byte(field.Index(j).Uint()))
+				}
+			case reflect.String:
+				var length int
+				for j := 0; j < field.Len(); j++ {
+					if j != 0 {
+						length++ /* comma */
+					}
+					length += len(field.Index(j).String())
+				}
+
+				out = append(out, byte(length>>24))
+				out = append(out, byte(length>>16))
+				out = append(out, byte(length>>8))
+				out = append(out, byte(length))
+				for j := 0; j < field.Len(); j++ {
+					if j != 0 {
+						out = append(out, ',')
+					}
+					out = append(out, field.Index(j).String()...)
+				}
+			default:
+				panic("slice of unknown type")
+			}
+		case reflect.Ptr:
+			if t == bigIntType {
+				var n *big.Int
+				nValue := reflect.ValueOf(&n)
+				nValue.Elem().Set(field)
+				needed := intLength(n)
+				oldLength := len(out)
+
+				if cap(out)-len(out) < needed {
+					newOut := make([]byte, len(out), 2*(len(out)+needed))
+					copy(newOut, out)
+					out = newOut
+				}
+				out = out[:oldLength+needed]
+				marshalInt(out[oldLength:], n)
+			} else {
+				panic("pointer to unknown type")
+			}
+		}
+	}
+
+	return out
+}
+
+var bigOne = big.NewInt(1)
+
+func parseString(in []byte) (out, rest []byte, ok bool) {
+	if len(in) < 4 {
+		return
+	}
+	length := uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
+	if uint32(len(in)) < 4+length {
+		return
+	}
+	out = in[4 : 4+length]
+	rest = in[4+length:]
+	ok = true
+	return
+}
+
+var (
+	comma         = []byte{','}
+	emptyNameList = []string{}
+)
+
+func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
+	contents, rest, ok := parseString(in)
+	if !ok {
+		return
+	}
+	if len(contents) == 0 {
+		out = emptyNameList
+		return
+	}
+	parts := bytes.Split(contents, comma)
+	out = make([]string, len(parts))
+	for i, part := range parts {
+		out[i] = string(part)
+	}
+	return
+}
+
+func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
+	contents, rest, ok := parseString(in)
+	if !ok {
+		return
+	}
+	out = new(big.Int)
+
+	if len(contents) > 0 && contents[0]&0x80 == 0x80 {
+		// This is a negative number
+		notBytes := make([]byte, len(contents))
+		for i := range notBytes {
+			notBytes[i] = ^contents[i]
+		}
+		out.SetBytes(notBytes)
+		out.Add(out, bigOne)
+		out.Neg(out)
+	} else {
+		// Positive number
+		out.SetBytes(contents)
+	}
+	ok = true
+	return
+}
+
+func parseUint32(in []byte) (out uint32, rest []byte, ok bool) {
+	if len(in) < 4 {
+		return
+	}
+	out = uint32(in[0])<<24 | uint32(in[1])<<16 | uint32(in[2])<<8 | uint32(in[3])
+	rest = in[4:]
+	ok = true
+	return
+}
+
+func nameListLength(namelist []string) int {
+	length := 4 /* uint32 length prefix */
+	for i, name := range namelist {
+		if i != 0 {
+			length++ /* comma */
+		}
+		length += len(name)
+	}
+	return length
+}
+
+func intLength(n *big.Int) int {
+	length := 4 /* length bytes */
+	if n.Sign() < 0 {
+		nMinus1 := new(big.Int).Neg(n)
+		nMinus1.Sub(nMinus1, bigOne)
+		bitLen := nMinus1.BitLen()
+		if bitLen%8 == 0 {
+			// The number will need 0xff padding
+			length++
+		}
+		length += (bitLen + 7) / 8
+	} else if n.Sign() == 0 {
+		// A zero is the zero length string
+	} else {
+		bitLen := n.BitLen()
+		if bitLen%8 == 0 {
+			// The number will need 0x00 padding
+			length++
+		}
+		length += (bitLen + 7) / 8
+	}
+
+	return length
+}
+
+func marshalUint32(to []byte, n uint32) []byte {
+	to[0] = byte(n >> 24)
+	to[1] = byte(n >> 16)
+	to[2] = byte(n >> 8)
+	to[3] = byte(n)
+	return to[4:]
+}
+
+func marshalUint64(to []byte, n uint64) []byte {
+	to[0] = byte(n >> 56)
+	to[1] = byte(n >> 48)
+	to[2] = byte(n >> 40)
+	to[3] = byte(n >> 32)
+	to[4] = byte(n >> 24)
+	to[5] = byte(n >> 16)
+	to[6] = byte(n >> 8)
+	to[7] = byte(n)
+	return to[8:]
+}
+
+func marshalInt(to []byte, n *big.Int) []byte {
+	lengthBytes := to
+	to = to[4:]
+	length := 0
+
+	if n.Sign() < 0 {
+		// A negative number has to be converted to two's-complement
+		// form. So we'll subtract 1 and invert. If the
+		// most-significant-bit isn't set then we'll need to pad the
+		// beginning with 0xff in order to keep the number negative.
+		nMinus1 := new(big.Int).Neg(n)
+		nMinus1.Sub(nMinus1, bigOne)
+		bytes := nMinus1.Bytes()
+		for i := range bytes {
+			bytes[i] ^= 0xff
+		}
+		if len(bytes) == 0 || bytes[0]&0x80 == 0 {
+			to[0] = 0xff
+			to = to[1:]
+			length++
+		}
+		nBytes := copy(to, bytes)
+		to = to[nBytes:]
+		length += nBytes
+	} else if n.Sign() == 0 {
+		// A zero is the zero length string
+	} else {
+		bytes := n.Bytes()
+		if len(bytes) > 0 && bytes[0]&0x80 != 0 {
+			// We'll have to pad this with a 0x00 in order to
+			// stop it looking like a negative number.
+			to[0] = 0
+			to = to[1:]
+			length++
+		}
+		nBytes := copy(to, bytes)
+		to = to[nBytes:]
+		length += nBytes
+	}
+
+	lengthBytes[0] = byte(length >> 24)
+	lengthBytes[1] = byte(length >> 16)
+	lengthBytes[2] = byte(length >> 8)
+	lengthBytes[3] = byte(length)
+	return to
+}
+
+func writeInt(w io.Writer, n *big.Int) {
+	length := intLength(n)
+	buf := make([]byte, length)
+	marshalInt(buf, n)
+	w.Write(buf)
+}
+
+func writeString(w io.Writer, s []byte) {
+	var lengthBytes [4]byte
+	lengthBytes[0] = byte(len(s) >> 24)
+	lengthBytes[1] = byte(len(s) >> 16)
+	lengthBytes[2] = byte(len(s) >> 8)
+	lengthBytes[3] = byte(len(s))
+	w.Write(lengthBytes[:])
+	w.Write(s)
+}
+
+func stringLength(s []byte) int {
+	return 4 + len(s)
+}
+
+func marshalString(to []byte, s []byte) []byte {
+	to[0] = byte(len(s) >> 24)
+	to[1] = byte(len(s) >> 16)
+	to[2] = byte(len(s) >> 8)
+	to[3] = byte(len(s))
+	to = to[4:]
+	copy(to, s)
+	return to[len(s):]
+}
+
+var bigIntType = reflect.TypeOf((*big.Int)(nil))
+
+// Decode a packet into it's corresponding message.
+func decode(packet []byte) interface{} {
+	var msg interface{}
+	switch packet[0] {
+	case msgDisconnect:
+		msg = new(disconnectMsg)
+	case msgServiceRequest:
+		msg = new(serviceRequestMsg)
+	case msgServiceAccept:
+		msg = new(serviceAcceptMsg)
+	case msgKexInit:
+		msg = new(kexInitMsg)
+	case msgKexDHInit:
+		msg = new(kexDHInitMsg)
+	case msgKexDHReply:
+		msg = new(kexDHReplyMsg)
+	case msgUserAuthRequest:
+		msg = new(userAuthRequestMsg)
+	case msgUserAuthFailure:
+		msg = new(userAuthFailureMsg)
+	case msgUserAuthPubKeyOk:
+		msg = new(userAuthPubKeyOkMsg)
+	case msgGlobalRequest:
+		msg = new(globalRequestMsg)
+	case msgRequestSuccess:
+		msg = new(channelRequestSuccessMsg)
+	case msgRequestFailure:
+		msg = new(channelRequestFailureMsg)
+	case msgChannelOpen:
+		msg = new(channelOpenMsg)
+	case msgChannelOpenConfirm:
+		msg = new(channelOpenConfirmMsg)
+	case msgChannelOpenFailure:
+		msg = new(channelOpenFailureMsg)
+	case msgChannelWindowAdjust:
+		msg = new(windowAdjustMsg)
+	case msgChannelEOF:
+		msg = new(channelEOFMsg)
+	case msgChannelClose:
+		msg = new(channelCloseMsg)
+	case msgChannelRequest:
+		msg = new(channelRequestMsg)
+	case msgChannelSuccess:
+		msg = new(channelRequestSuccessMsg)
+	case msgChannelFailure:
+		msg = new(channelRequestFailureMsg)
+	default:
+		return UnexpectedMessageError{0, packet[0]}
+	}
+	if err := unmarshal(msg, packet, packet[0]); err != nil {
+		return err
+	}
+	return msg
+}
diff --git a/ssh/messages_test.go b/ssh/messages_test.go
new file mode 100644
index 0000000..fe4c397
--- /dev/null
+++ b/ssh/messages_test.go
@@ -0,0 +1,125 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"math/big"
+	"math/rand"
+	"reflect"
+	"testing"
+	"testing/quick"
+)
+
+var intLengthTests = []struct {
+	val, length int
+}{
+	{0, 4 + 0},
+	{1, 4 + 1},
+	{127, 4 + 1},
+	{128, 4 + 2},
+	{-1, 4 + 1},
+}
+
+func TestIntLength(t *testing.T) {
+	for _, test := range intLengthTests {
+		v := new(big.Int).SetInt64(int64(test.val))
+		length := intLength(v)
+		if length != test.length {
+			t.Errorf("For %d, got length %d but expected %d", test.val, length, test.length)
+		}
+	}
+}
+
+var messageTypes = []interface{}{
+	&kexInitMsg{},
+	&kexDHInitMsg{},
+	&serviceRequestMsg{},
+	&serviceAcceptMsg{},
+	&userAuthRequestMsg{},
+	&channelOpenMsg{},
+	&channelOpenConfirmMsg{},
+	&channelRequestMsg{},
+	&channelRequestSuccessMsg{},
+}
+
+func TestMarshalUnmarshal(t *testing.T) {
+	rand := rand.New(rand.NewSource(0))
+	for i, iface := range messageTypes {
+		ty := reflect.ValueOf(iface).Type()
+
+		n := 100
+		if testing.Short() {
+			n = 5
+		}
+		for j := 0; j < n; j++ {
+			v, ok := quick.Value(ty, rand)
+			if !ok {
+				t.Errorf("#%d: failed to create value", i)
+				break
+			}
+
+			m1 := v.Elem().Interface()
+			m2 := iface
+
+			marshaled := marshal(msgIgnore, m1)
+			if err := unmarshal(m2, marshaled, msgIgnore); err != nil {
+				t.Errorf("#%d failed to unmarshal %#v: %s", i, m1, err)
+				break
+			}
+
+			if !reflect.DeepEqual(v.Interface(), m2) {
+				t.Errorf("#%d\ngot: %#v\nwant:%#v\n%x", i, m2, m1, marshaled)
+				break
+			}
+		}
+	}
+}
+
+func randomBytes(out []byte, rand *rand.Rand) {
+	for i := 0; i < len(out); i++ {
+		out[i] = byte(rand.Int31())
+	}
+}
+
+func randomNameList(rand *rand.Rand) []string {
+	ret := make([]string, rand.Int31()&15)
+	for i := range ret {
+		s := make([]byte, 1+(rand.Int31()&15))
+		for j := range s {
+			s[j] = 'a' + uint8(rand.Int31()&15)
+		}
+		ret[i] = string(s)
+	}
+	return ret
+}
+
+func randomInt(rand *rand.Rand) *big.Int {
+	return new(big.Int).SetInt64(int64(int32(rand.Uint32())))
+}
+
+func (*kexInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+	ki := &kexInitMsg{}
+	randomBytes(ki.Cookie[:], rand)
+	ki.KexAlgos = randomNameList(rand)
+	ki.ServerHostKeyAlgos = randomNameList(rand)
+	ki.CiphersClientServer = randomNameList(rand)
+	ki.CiphersServerClient = randomNameList(rand)
+	ki.MACsClientServer = randomNameList(rand)
+	ki.MACsServerClient = randomNameList(rand)
+	ki.CompressionClientServer = randomNameList(rand)
+	ki.CompressionServerClient = randomNameList(rand)
+	ki.LanguagesClientServer = randomNameList(rand)
+	ki.LanguagesServerClient = randomNameList(rand)
+	if rand.Int31()&1 == 1 {
+		ki.FirstKexFollows = true
+	}
+	return reflect.ValueOf(ki)
+}
+
+func (*kexDHInitMsg) Generate(rand *rand.Rand, size int) reflect.Value {
+	dhi := &kexDHInitMsg{}
+	dhi.X = randomInt(rand)
+	return reflect.ValueOf(dhi)
+}
diff --git a/ssh/server.go b/ssh/server.go
new file mode 100644
index 0000000..31011c6
--- /dev/null
+++ b/ssh/server.go
@@ -0,0 +1,676 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"io"
+	"math/big"
+	"net"
+	"sync"
+)
+
+type ServerConfig struct {
+	rsa           *rsa.PrivateKey
+	rsaSerialized []byte
+
+	// Rand provides the source of entropy for key exchange. If Rand is 
+	// nil, the cryptographic random reader in package crypto/rand will 
+	// be used.
+	Rand io.Reader
+
+	// NoClientAuth is true if clients are allowed to connect without
+	// authenticating.
+	NoClientAuth bool
+
+	// PasswordCallback, if non-nil, is called when a user attempts to
+	// authenticate using a password. It may be called concurrently from
+	// several goroutines.
+	PasswordCallback func(user, password string) bool
+
+	// PublicKeyCallback, if non-nil, is called when a client attempts public
+	// key authentication. It must return true iff the given public key is
+	// valid for the given user.
+	PublicKeyCallback func(user, algo string, pubkey []byte) bool
+
+	// Cryptographic-related configuration.
+	Crypto CryptoConfig
+}
+
+func (c *ServerConfig) rand() io.Reader {
+	if c.Rand == nil {
+		return rand.Reader
+	}
+	return c.Rand
+}
+
+// SetRSAPrivateKey sets the private key for a Server. A Server must have a
+// private key configured in order to accept connections. The private key must
+// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
+// typically contains such a key.
+func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
+	block, _ := pem.Decode(pemBytes)
+	if block == nil {
+		return errors.New("ssh: no key found")
+	}
+	var err error
+	s.rsa, err = x509.ParsePKCS1PrivateKey(block.Bytes)
+	if err != nil {
+		return err
+	}
+
+	s.rsaSerialized = marshalRSA(s.rsa)
+	return nil
+}
+
+// marshalRSA serializes an RSA private key according to RFC 4256, section 6.6.
+func marshalRSA(priv *rsa.PrivateKey) []byte {
+	e := new(big.Int).SetInt64(int64(priv.E))
+	length := stringLength([]byte(hostAlgoRSA))
+	length += intLength(e)
+	length += intLength(priv.N)
+
+	ret := make([]byte, length)
+	r := marshalString(ret, []byte(hostAlgoRSA))
+	r = marshalInt(r, e)
+	r = marshalInt(r, priv.N)
+
+	return ret
+}
+
+// parseRSA parses an RSA key according to RFC 4256, section 6.6.
+func parseRSA(in []byte) (pubKey *rsa.PublicKey, ok bool) {
+	algo, in, ok := parseString(in)
+	if !ok || string(algo) != hostAlgoRSA {
+		return nil, false
+	}
+	bigE, in, ok := parseInt(in)
+	if !ok || bigE.BitLen() > 24 {
+		return nil, false
+	}
+	e := bigE.Int64()
+	if e < 3 || e&1 == 0 {
+		return nil, false
+	}
+	N, in, ok := parseInt(in)
+	if !ok || len(in) > 0 {
+		return nil, false
+	}
+	return &rsa.PublicKey{
+		N: N,
+		E: int(e),
+	}, true
+}
+
+func parseRSASig(in []byte) (sig []byte, ok bool) {
+	algo, in, ok := parseString(in)
+	if !ok || string(algo) != hostAlgoRSA {
+		return nil, false
+	}
+	sig, in, ok = parseString(in)
+	if len(in) > 0 {
+		ok = false
+	}
+	return
+}
+
+// cachedPubKey contains the results of querying whether a public key is
+// acceptable for a user. The cache only applies to a single ServerConn.
+type cachedPubKey struct {
+	user, algo string
+	pubKey     []byte
+	result     bool
+}
+
+const maxCachedPubKeys = 16
+
+// A ServerConn represents an incomming connection.
+type ServerConn struct {
+	*transport
+	config *ServerConfig
+
+	channels   map[uint32]*channel
+	nextChanId uint32
+
+	// lock protects err and also allows Channels to serialise their writes
+	// to out.
+	lock sync.RWMutex
+	err  error
+
+	// cachedPubKeys contains the cache results of tests for public keys.
+	// Since SSH clients will query whether a public key is acceptable
+	// before attempting to authenticate with it, we end up with duplicate
+	// queries for public key validity.
+	cachedPubKeys []cachedPubKey
+}
+
+// Server returns a new SSH server connection
+// using c as the underlying transport.
+func Server(c net.Conn, config *ServerConfig) *ServerConn {
+	conn := &ServerConn{
+		transport: newTransport(c, config.rand()),
+		channels:  make(map[uint32]*channel),
+		config:    config,
+	}
+	return conn
+}
+
+// kexDH performs Diffie-Hellman key agreement on a ServerConnection. The
+// returned values are given the same names as in RFC 4253, section 8.
+func (s *ServerConn) kexDH(group *dhGroup, hashFunc crypto.Hash, magics *handshakeMagics, hostKeyAlgo string) (H, K []byte, err error) {
+	packet, err := s.readPacket()
+	if err != nil {
+		return
+	}
+	var kexDHInit kexDHInitMsg
+	if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil {
+		return
+	}
+
+	if kexDHInit.X.Sign() == 0 || kexDHInit.X.Cmp(group.p) >= 0 {
+		return nil, nil, errors.New("client DH parameter out of bounds")
+	}
+
+	y, err := rand.Int(s.config.rand(), group.p)
+	if err != nil {
+		return
+	}
+
+	Y := new(big.Int).Exp(group.g, y, group.p)
+	kInt := new(big.Int).Exp(kexDHInit.X, y, group.p)
+
+	var serializedHostKey []byte
+	switch hostKeyAlgo {
+	case hostAlgoRSA:
+		serializedHostKey = s.config.rsaSerialized
+	default:
+		return nil, nil, errors.New("internal error")
+	}
+
+	h := hashFunc.New()
+	writeString(h, magics.clientVersion)
+	writeString(h, magics.serverVersion)
+	writeString(h, magics.clientKexInit)
+	writeString(h, magics.serverKexInit)
+	writeString(h, serializedHostKey)
+	writeInt(h, kexDHInit.X)
+	writeInt(h, Y)
+	K = make([]byte, intLength(kInt))
+	marshalInt(K, kInt)
+	h.Write(K)
+
+	H = h.Sum(nil)
+
+	h.Reset()
+	h.Write(H)
+	hh := h.Sum(nil)
+
+	var sig []byte
+	switch hostKeyAlgo {
+	case hostAlgoRSA:
+		sig, err = rsa.SignPKCS1v15(s.config.rand(), s.config.rsa, hashFunc, hh)
+		if err != nil {
+			return
+		}
+	default:
+		return nil, nil, errors.New("internal error")
+	}
+
+	serializedSig := serializeSignature(hostAlgoRSA, sig)
+
+	kexDHReply := kexDHReplyMsg{
+		HostKey:   serializedHostKey,
+		Y:         Y,
+		Signature: serializedSig,
+	}
+	packet = marshal(msgKexDHReply, kexDHReply)
+
+	err = s.writePacket(packet)
+	return
+}
+
+// serverVersion is the fixed identification string that Server will use.
+var serverVersion = []byte("SSH-2.0-Go\r\n")
+
+// Handshake performs an SSH transport and client authentication on the given ServerConn.
+func (s *ServerConn) Handshake() error {
+	var magics handshakeMagics
+	if _, err := s.Write(serverVersion); err != nil {
+		return err
+	}
+	if err := s.Flush(); err != nil {
+		return err
+	}
+	magics.serverVersion = serverVersion[:len(serverVersion)-2]
+
+	version, err := readVersion(s)
+	if err != nil {
+		return err
+	}
+	magics.clientVersion = version
+
+	serverKexInit := kexInitMsg{
+		KexAlgos:                supportedKexAlgos,
+		ServerHostKeyAlgos:      supportedHostKeyAlgos,
+		CiphersClientServer:     s.config.Crypto.ciphers(),
+		CiphersServerClient:     s.config.Crypto.ciphers(),
+		MACsClientServer:        supportedMACs,
+		MACsServerClient:        supportedMACs,
+		CompressionClientServer: supportedCompressions,
+		CompressionServerClient: supportedCompressions,
+	}
+	kexInitPacket := marshal(msgKexInit, serverKexInit)
+	magics.serverKexInit = kexInitPacket
+
+	if err := s.writePacket(kexInitPacket); err != nil {
+		return err
+	}
+
+	packet, err := s.readPacket()
+	if err != nil {
+		return err
+	}
+
+	magics.clientKexInit = packet
+
+	var clientKexInit kexInitMsg
+	if err = unmarshal(&clientKexInit, packet, msgKexInit); err != nil {
+		return err
+	}
+
+	kexAlgo, hostKeyAlgo, ok := findAgreedAlgorithms(s.transport, &clientKexInit, &serverKexInit)
+	if !ok {
+		return errors.New("ssh: no common algorithms")
+	}
+
+	if clientKexInit.FirstKexFollows && kexAlgo != clientKexInit.KexAlgos[0] {
+		// The client sent a Kex message for the wrong algorithm,
+		// which we have to ignore.
+		if _, err := s.readPacket(); err != nil {
+			return err
+		}
+	}
+
+	var H, K []byte
+	var hashFunc crypto.Hash
+	switch kexAlgo {
+	case kexAlgoDH14SHA1:
+		hashFunc = crypto.SHA1
+		dhGroup14Once.Do(initDHGroup14)
+		H, K, err = s.kexDH(dhGroup14, hashFunc, &magics, hostKeyAlgo)
+	default:
+		err = errors.New("ssh: unexpected key exchange algorithm " + kexAlgo)
+	}
+	if err != nil {
+		return err
+	}
+
+	if err = s.writePacket([]byte{msgNewKeys}); err != nil {
+		return err
+	}
+	if err = s.transport.writer.setupKeys(serverKeys, K, H, H, hashFunc); err != nil {
+		return err
+	}
+	if packet, err = s.readPacket(); err != nil {
+		return err
+	}
+
+	if packet[0] != msgNewKeys {
+		return UnexpectedMessageError{msgNewKeys, packet[0]}
+	}
+	if err = s.transport.reader.setupKeys(clientKeys, K, H, H, hashFunc); err != nil {
+		return err
+	}
+	if packet, err = s.readPacket(); err != nil {
+		return err
+	}
+
+	var serviceRequest serviceRequestMsg
+	if err = unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil {
+		return err
+	}
+	if serviceRequest.Service != serviceUserAuth {
+		return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
+	}
+	serviceAccept := serviceAcceptMsg{
+		Service: serviceUserAuth,
+	}
+	if err = s.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
+		return err
+	}
+
+	if err = s.authenticate(H); err != nil {
+		return err
+	}
+	return nil
+}
+
+func isAcceptableAlgo(algo string) bool {
+	return algo == hostAlgoRSA
+}
+
+// testPubKey returns true if the given public key is acceptable for the user.
+func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
+	if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) {
+		return false
+	}
+
+	for _, c := range s.cachedPubKeys {
+		if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) {
+			return c.result
+		}
+	}
+
+	result := s.config.PublicKeyCallback(user, algo, pubKey)
+	if len(s.cachedPubKeys) < maxCachedPubKeys {
+		c := cachedPubKey{
+			user:   user,
+			algo:   algo,
+			pubKey: make([]byte, len(pubKey)),
+			result: result,
+		}
+		copy(c.pubKey, pubKey)
+		s.cachedPubKeys = append(s.cachedPubKeys, c)
+	}
+
+	return result
+}
+
+func (s *ServerConn) authenticate(H []byte) error {
+	var userAuthReq userAuthRequestMsg
+	var err error
+	var packet []byte
+
+userAuthLoop:
+	for {
+		if packet, err = s.readPacket(); err != nil {
+			return err
+		}
+		if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil {
+			return err
+		}
+
+		if userAuthReq.Service != serviceSSH {
+			return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
+		}
+
+		switch userAuthReq.Method {
+		case "none":
+			if s.config.NoClientAuth {
+				break userAuthLoop
+			}
+		case "password":
+			if s.config.PasswordCallback == nil {
+				break
+			}
+			payload := userAuthReq.Payload
+			if len(payload) < 1 || payload[0] != 0 {
+				return ParseError{msgUserAuthRequest}
+			}
+			payload = payload[1:]
+			password, payload, ok := parseString(payload)
+			if !ok || len(payload) > 0 {
+				return ParseError{msgUserAuthRequest}
+			}
+
+			if s.config.PasswordCallback(userAuthReq.User, string(password)) {
+				break userAuthLoop
+			}
+		case "publickey":
+			if s.config.PublicKeyCallback == nil {
+				break
+			}
+			payload := userAuthReq.Payload
+			if len(payload) < 1 {
+				return ParseError{msgUserAuthRequest}
+			}
+			isQuery := payload[0] == 0
+			payload = payload[1:]
+			algoBytes, payload, ok := parseString(payload)
+			if !ok {
+				return ParseError{msgUserAuthRequest}
+			}
+			algo := string(algoBytes)
+
+			pubKey, payload, ok := parseString(payload)
+			if !ok {
+				return ParseError{msgUserAuthRequest}
+			}
+			if isQuery {
+				// The client can query if the given public key
+				// would be ok.
+				if len(payload) > 0 {
+					return ParseError{msgUserAuthRequest}
+				}
+				if s.testPubKey(userAuthReq.User, algo, pubKey) {
+					okMsg := userAuthPubKeyOkMsg{
+						Algo:   algo,
+						PubKey: string(pubKey),
+					}
+					if err = s.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil {
+						return err
+					}
+					continue userAuthLoop
+				}
+			} else {
+				sig, payload, ok := parseString(payload)
+				if !ok || len(payload) > 0 {
+					return ParseError{msgUserAuthRequest}
+				}
+				if !isAcceptableAlgo(algo) {
+					break
+				}
+				rsaSig, ok := parseRSASig(sig)
+				if !ok {
+					return ParseError{msgUserAuthRequest}
+				}
+				signedData := buildDataSignedForAuth(H, userAuthReq, algoBytes, pubKey)
+				switch algo {
+				case hostAlgoRSA:
+					hashFunc := crypto.SHA1
+					h := hashFunc.New()
+					h.Write(signedData)
+					digest := h.Sum(nil)
+					rsaKey, ok := parseRSA(pubKey)
+					if !ok {
+						return ParseError{msgUserAuthRequest}
+					}
+					if rsa.VerifyPKCS1v15(rsaKey, hashFunc, digest, rsaSig) != nil {
+						return ParseError{msgUserAuthRequest}
+					}
+				default:
+					return errors.New("ssh: isAcceptableAlgo incorrect")
+				}
+				if s.testPubKey(userAuthReq.User, algo, pubKey) {
+					break userAuthLoop
+				}
+			}
+		}
+
+		var failureMsg userAuthFailureMsg
+		if s.config.PasswordCallback != nil {
+			failureMsg.Methods = append(failureMsg.Methods, "password")
+		}
+		if s.config.PublicKeyCallback != nil {
+			failureMsg.Methods = append(failureMsg.Methods, "publickey")
+		}
+
+		if len(failureMsg.Methods) == 0 {
+			return errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
+		}
+
+		if err = s.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil {
+			return err
+		}
+	}
+
+	packet = []byte{msgUserAuthSuccess}
+	if err = s.writePacket(packet); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+const defaultWindowSize = 32768
+
+// Accept reads and processes messages on a ServerConn. It must be called
+// in order to demultiplex messages to any resulting Channels.
+func (s *ServerConn) Accept() (Channel, error) {
+	if s.err != nil {
+		return nil, s.err
+	}
+
+	for {
+		packet, err := s.readPacket()
+		if err != nil {
+
+			s.lock.Lock()
+			s.err = err
+			s.lock.Unlock()
+
+			for _, c := range s.channels {
+				c.dead = true
+				c.handleData(nil)
+			}
+
+			return nil, err
+		}
+
+		switch packet[0] {
+		case msgChannelData:
+			if len(packet) < 9 {
+				// malformed data packet
+				return nil, ParseError{msgChannelData}
+			}
+			peersId := uint32(packet[1])<<24 | uint32(packet[2])<<16 | uint32(packet[3])<<8 | uint32(packet[4])
+			s.lock.Lock()
+			c, ok := s.channels[peersId]
+			if !ok {
+				s.lock.Unlock()
+				continue
+			}
+			if length := int(packet[5])<<24 | int(packet[6])<<16 | int(packet[7])<<8 | int(packet[8]); length > 0 {
+				packet = packet[9:]
+				c.handleData(packet[:length])
+			}
+			s.lock.Unlock()
+		default:
+			switch msg := decode(packet).(type) {
+			case *channelOpenMsg:
+				c := new(channel)
+				c.chanType = msg.ChanType
+				c.theirId = msg.PeersId
+				c.theirWindow = msg.PeersWindow
+				c.maxPacketSize = msg.MaxPacketSize
+				c.extraData = msg.TypeSpecificData
+				c.myWindow = defaultWindowSize
+				c.serverConn = s
+				c.cond = sync.NewCond(&c.lock)
+				c.pendingData = make([]byte, c.myWindow)
+
+				s.lock.Lock()
+				c.myId = s.nextChanId
+				s.nextChanId++
+				s.channels[c.myId] = c
+				s.lock.Unlock()
+				return c, nil
+
+			case *channelRequestMsg:
+				s.lock.Lock()
+				c, ok := s.channels[msg.PeersId]
+				if !ok {
+					s.lock.Unlock()
+					continue
+				}
+				c.handlePacket(msg)
+				s.lock.Unlock()
+
+			case *channelEOFMsg:
+				s.lock.Lock()
+				c, ok := s.channels[msg.PeersId]
+				if !ok {
+					s.lock.Unlock()
+					continue
+				}
+				c.handlePacket(msg)
+				s.lock.Unlock()
+
+			case *channelCloseMsg:
+				s.lock.Lock()
+				c, ok := s.channels[msg.PeersId]
+				if !ok {
+					s.lock.Unlock()
+					continue
+				}
+				c.handlePacket(msg)
+				s.lock.Unlock()
+
+			case *globalRequestMsg:
+				if msg.WantReply {
+					if err := s.writePacket([]byte{msgRequestFailure}); err != nil {
+						return nil, err
+					}
+				}
+
+			case UnexpectedMessageError:
+				return nil, msg
+			case *disconnectMsg:
+				return nil, io.EOF
+			default:
+				// Unknown message. Ignore.
+			}
+		}
+	}
+
+	panic("unreachable")
+}
+
+// A Listener implements a network listener (net.Listener) for SSH connections.
+type Listener struct {
+	listener net.Listener
+	config   *ServerConfig
+}
+
+// Accept waits for and returns the next incoming SSH connection.
+// The receiver should call Handshake() in another goroutine 
+// to avoid blocking the accepter.
+func (l *Listener) Accept() (*ServerConn, error) {
+	c, err := l.listener.Accept()
+	if err != nil {
+		return nil, err
+	}
+	conn := Server(c, l.config)
+	return conn, nil
+}
+
+// Addr returns the listener's network address.
+func (l *Listener) Addr() net.Addr {
+	return l.listener.Addr()
+}
+
+// Close closes the listener.
+func (l *Listener) Close() error {
+	return l.listener.Close()
+}
+
+// Listen creates an SSH listener accepting connections on
+// the given network address using net.Listen.
+func Listen(network, addr string, config *ServerConfig) (*Listener, error) {
+	l, err := net.Listen(network, addr)
+	if err != nil {
+		return nil, err
+	}
+	return &Listener{
+		l,
+		config,
+	}, nil
+}
diff --git a/ssh/server_terminal.go b/ssh/server_terminal.go
new file mode 100644
index 0000000..708a915
--- /dev/null
+++ b/ssh/server_terminal.go
@@ -0,0 +1,81 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// A Terminal is capable of parsing and generating virtual terminal
+// data from an SSH client.
+type Terminal interface {
+	ReadLine() (line string, err error)
+	SetSize(x, y int)
+	Write([]byte) (int, error)
+}
+
+// ServerTerminal contains the state for running a terminal that is capable of
+// reading lines of input.
+type ServerTerminal struct {
+	Term    Terminal
+	Channel Channel
+}
+
+// parsePtyRequest parses the payload of the pty-req message and extracts the
+// dimensions of the terminal. See RFC 4254, section 6.2.
+func parsePtyRequest(s []byte) (width, height int, ok bool) {
+	_, s, ok = parseString(s)
+	if !ok {
+		return
+	}
+	width32, s, ok := parseUint32(s)
+	if !ok {
+		return
+	}
+	height32, _, ok := parseUint32(s)
+	width = int(width32)
+	height = int(height32)
+	if width < 1 {
+		ok = false
+	}
+	if height < 1 {
+		ok = false
+	}
+	return
+}
+
+func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
+	return ss.Term.Write(buf)
+}
+
+// ReadLine returns a line of input from the terminal.
+func (ss *ServerTerminal) ReadLine() (line string, err error) {
+	for {
+		if line, err = ss.Term.ReadLine(); err == nil {
+			return
+		}
+
+		req, ok := err.(ChannelRequest)
+		if !ok {
+			return
+		}
+
+		ok = false
+		switch req.Request {
+		case "pty-req":
+			var width, height int
+			width, height, ok = parsePtyRequest(req.Payload)
+			ss.Term.SetSize(width, height)
+		case "shell":
+			ok = true
+			if len(req.Payload) > 0 {
+				// We don't accept any commands, only the default shell.
+				ok = false
+			}
+		case "env":
+			ok = true
+		}
+		if req.WantReply {
+			ss.Channel.AckRequest(ok)
+		}
+	}
+	panic("unreachable")
+}
diff --git a/ssh/session.go b/ssh/session.go
new file mode 100644
index 0000000..ea4addb
--- /dev/null
+++ b/ssh/session.go
@@ -0,0 +1,494 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// Session implements an interactive session described in
+// "RFC 4254, section 6".
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+)
+
+type Signal string
+
+// POSIX signals as listed in RFC 4254 Section 6.10.
+const (
+	SIGABRT Signal = "ABRT"
+	SIGALRM Signal = "ALRM"
+	SIGFPE  Signal = "FPE"
+	SIGHUP  Signal = "HUP"
+	SIGILL  Signal = "ILL"
+	SIGINT  Signal = "INT"
+	SIGKILL Signal = "KILL"
+	SIGPIPE Signal = "PIPE"
+	SIGQUIT Signal = "QUIT"
+	SIGSEGV Signal = "SEGV"
+	SIGTERM Signal = "TERM"
+	SIGUSR1 Signal = "USR1"
+	SIGUSR2 Signal = "USR2"
+)
+
+var signals = map[Signal]int{
+	SIGABRT: 6,
+	SIGALRM: 14,
+	SIGFPE:  8,
+	SIGHUP:  1,
+	SIGILL:  4,
+	SIGINT:  2,
+	SIGKILL: 9,
+	SIGPIPE: 13,
+	SIGQUIT: 3,
+	SIGSEGV: 11,
+	SIGTERM: 15,
+}
+
+// A Session represents a connection to a remote command or shell.
+type Session struct {
+	// Stdin specifies the remote process's standard input.
+	// If Stdin is nil, the remote process reads from an empty
+	// bytes.Buffer.
+	Stdin io.Reader
+
+	// Stdout and Stderr specify the remote process's standard
+	// output and error.
+	//
+	// If either is nil, Run connects the corresponding file
+	// descriptor to an instance of ioutil.Discard. There is a
+	// fixed amount of buffering that is shared for the two streams.
+	// If either blocks it may eventually cause the remote
+	// command to block.
+	Stdout io.Writer
+	Stderr io.Writer
+
+	*clientChan // the channel backing this session
+
+	started   bool // true once Start, Run or Shell is invoked.
+	copyFuncs []func() error
+	errors    chan error // one send per copyFunc
+
+	// true if pipe method is active
+	stdinpipe, stdoutpipe, stderrpipe bool
+}
+
+// RFC 4254 Section 6.4.
+type setenvRequest struct {
+	PeersId   uint32
+	Request   string
+	WantReply bool
+	Name      string
+	Value     string
+}
+
+// Setenv sets an environment variable that will be applied to any
+// command executed by Shell or Run.
+func (s *Session) Setenv(name, value string) error {
+	req := setenvRequest{
+		PeersId:   s.peersId,
+		Request:   "env",
+		WantReply: true,
+		Name:      name,
+		Value:     value,
+	}
+	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+		return err
+	}
+	return s.waitForResponse()
+}
+
+// An empty mode list, see RFC 4254 Section 8.
+var emptyModelist = "\x00"
+
+// RFC 4254 Section 6.2.
+type ptyRequestMsg struct {
+	PeersId   uint32
+	Request   string
+	WantReply bool
+	Term      string
+	Columns   uint32
+	Rows      uint32
+	Width     uint32
+	Height    uint32
+	Modelist  string
+}
+
+// RequestPty requests the association of a pty with the session on the remote host.
+func (s *Session) RequestPty(term string, h, w int) error {
+	req := ptyRequestMsg{
+		PeersId:   s.peersId,
+		Request:   "pty-req",
+		WantReply: true,
+		Term:      term,
+		Columns:   uint32(w),
+		Rows:      uint32(h),
+		Width:     uint32(w * 8),
+		Height:    uint32(h * 8),
+		Modelist:  emptyModelist,
+	}
+	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+		return err
+	}
+	return s.waitForResponse()
+}
+
+// RFC 4254 Section 6.9.
+type signalMsg struct {
+	PeersId   uint32
+	Request   string
+	WantReply bool
+	Signal    string
+}
+
+// Signal sends the given signal to the remote process.
+// sig is one of the SIG* constants.
+func (s *Session) Signal(sig Signal) error {
+	req := signalMsg{
+		PeersId:   s.peersId,
+		Request:   "signal",
+		WantReply: false,
+		Signal:    string(sig),
+	}
+	return s.writePacket(marshal(msgChannelRequest, req))
+}
+
+// RFC 4254 Section 6.5.
+type execMsg struct {
+	PeersId   uint32
+	Request   string
+	WantReply bool
+	Command   string
+}
+
+// Start runs cmd on the remote host. Typically, the remote
+// server passes cmd to the shell for interpretation.
+// A Session only accepts one call to Run, Start or Shell.
+func (s *Session) Start(cmd string) error {
+	if s.started {
+		return errors.New("ssh: session already started")
+	}
+	req := execMsg{
+		PeersId:   s.peersId,
+		Request:   "exec",
+		WantReply: true,
+		Command:   cmd,
+	}
+	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+		return err
+	}
+	if err := s.waitForResponse(); err != nil {
+		return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
+	}
+	return s.start()
+}
+
+// Run runs cmd on the remote host. Typically, the remote
+// server passes cmd to the shell for interpretation.
+// A Session only accepts one call to Run, Start or Shell.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+func (s *Session) Run(cmd string) error {
+	err := s.Start(cmd)
+	if err != nil {
+		return err
+	}
+	return s.Wait()
+}
+
+// Shell starts a login shell on the remote host. A Session only
+// accepts one call to Run, Start or Shell.
+func (s *Session) Shell() error {
+	if s.started {
+		return errors.New("ssh: session already started")
+	}
+	req := channelRequestMsg{
+		PeersId:   s.peersId,
+		Request:   "shell",
+		WantReply: true,
+	}
+	if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
+		return err
+	}
+	if err := s.waitForResponse(); err != nil {
+		return fmt.Errorf("ssh: cound not execute shell: %v", err)
+	}
+	return s.start()
+}
+
+func (s *Session) waitForResponse() error {
+	msg := <-s.msg
+	switch msg.(type) {
+	case *channelRequestSuccessMsg:
+		return nil
+	case *channelRequestFailureMsg:
+		return errors.New("request failed")
+	}
+	return fmt.Errorf("unknown packet %T received: %v", msg, msg)
+}
+
+func (s *Session) start() error {
+	s.started = true
+
+	type F func(*Session)
+	for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
+		setupFd(s)
+	}
+
+	s.errors = make(chan error, len(s.copyFuncs))
+	for _, fn := range s.copyFuncs {
+		go func(fn func() error) {
+			s.errors <- fn()
+		}(fn)
+	}
+	return nil
+}
+
+// Wait waits for the remote command to exit.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+func (s *Session) Wait() error {
+	if !s.started {
+		return errors.New("ssh: session not started")
+	}
+	waitErr := s.wait()
+
+	var copyError error
+	for _ = range s.copyFuncs {
+		if err := <-s.errors; err != nil && copyError == nil {
+			copyError = err
+		}
+	}
+	if waitErr != nil {
+		return waitErr
+	}
+	return copyError
+}
+
+func (s *Session) wait() error {
+	wm := Waitmsg{status: -1}
+
+	// Wait for msg channel to be closed before returning.
+	for msg := range s.msg {
+		switch msg := msg.(type) {
+		case *channelRequestMsg:
+			switch msg.Request {
+			case "exit-status":
+				d := msg.RequestSpecificData
+				wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
+			case "exit-signal":
+				signal, rest, ok := parseString(msg.RequestSpecificData)
+				if !ok {
+					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+				}
+				wm.signal = safeString(string(signal))
+
+				// skip coreDumped bool
+				if len(rest) == 0 {
+					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+				}
+				rest = rest[1:]
+
+				errmsg, rest, ok := parseString(rest)
+				if !ok {
+					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+				}
+				wm.msg = safeString(string(errmsg))
+
+				lang, _, ok := parseString(rest)
+				if !ok {
+					return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
+				}
+				wm.lang = safeString(string(lang))
+			default:
+				return fmt.Errorf("wait: unexpected channel request: %v", msg)
+			}
+		default:
+			return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
+		}
+	}
+	if wm.status == 0 {
+		return nil
+	}
+	if wm.status == -1 {
+		// exit-status was never sent from server
+		if wm.signal == "" {
+			return errors.New("wait: remote command exited without exit status or exit signal")
+		}
+		wm.status = 128
+		if _, ok := signals[Signal(wm.signal)]; ok {
+			wm.status += signals[Signal(wm.signal)]
+		}
+	}
+	return &ExitError{wm}
+}
+
+func (s *Session) stdin() {
+	if s.stdinpipe {
+		return
+	}
+	if s.Stdin == nil {
+		s.Stdin = new(bytes.Buffer)
+	}
+	s.copyFuncs = append(s.copyFuncs, func() error {
+		_, err := io.Copy(s.clientChan.stdin, s.Stdin)
+		if err1 := s.clientChan.stdin.Close(); err == nil {
+			err = err1
+		}
+		return err
+	})
+}
+
+func (s *Session) stdout() {
+	if s.stdoutpipe {
+		return
+	}
+	if s.Stdout == nil {
+		s.Stdout = ioutil.Discard
+	}
+	s.copyFuncs = append(s.copyFuncs, func() error {
+		_, err := io.Copy(s.Stdout, s.clientChan.stdout)
+		return err
+	})
+}
+
+func (s *Session) stderr() {
+	if s.stderrpipe {
+		return
+	}
+	if s.Stderr == nil {
+		s.Stderr = ioutil.Discard
+	}
+	s.copyFuncs = append(s.copyFuncs, func() error {
+		_, err := io.Copy(s.Stderr, s.clientChan.stderr)
+		return err
+	})
+}
+
+// StdinPipe returns a pipe that will be connected to the
+// remote command's standard input when the command starts.
+func (s *Session) StdinPipe() (io.WriteCloser, error) {
+	if s.Stdin != nil {
+		return nil, errors.New("ssh: Stdin already set")
+	}
+	if s.started {
+		return nil, errors.New("ssh: StdinPipe after process started")
+	}
+	s.stdinpipe = true
+	return s.clientChan.stdin, nil
+}
+
+// StdoutPipe returns a pipe that will be connected to the
+// remote command's standard output when the command starts.
+// There is a fixed amount of buffering that is shared between
+// stdout and stderr streams. If the StdoutPipe reader is
+// not serviced fast enought it may eventually cause the
+// remote command to block.
+func (s *Session) StdoutPipe() (io.Reader, error) {
+	if s.Stdout != nil {
+		return nil, errors.New("ssh: Stdout already set")
+	}
+	if s.started {
+		return nil, errors.New("ssh: StdoutPipe after process started")
+	}
+	s.stdoutpipe = true
+	return s.clientChan.stdout, nil
+}
+
+// StderrPipe returns a pipe that will be connected to the
+// remote command's standard error when the command starts.
+// There is a fixed amount of buffering that is shared between
+// stdout and stderr streams. If the StderrPipe reader is
+// not serviced fast enought it may eventually cause the
+// remote command to block.
+func (s *Session) StderrPipe() (io.Reader, error) {
+	if s.Stderr != nil {
+		return nil, errors.New("ssh: Stderr already set")
+	}
+	if s.started {
+		return nil, errors.New("ssh: StderrPipe after process started")
+	}
+	s.stderrpipe = true
+	return s.clientChan.stderr, nil
+}
+
+// TODO(dfc) add Output and CombinedOutput helpers
+
+// NewSession returns a new interactive session on the remote host.
+func (c *ClientConn) NewSession() (*Session, error) {
+	ch := c.newChan(c.transport)
+	if err := c.writePacket(marshal(msgChannelOpen, channelOpenMsg{
+		ChanType:      "session",
+		PeersId:       ch.id,
+		PeersWindow:   1 << 14,
+		MaxPacketSize: 1 << 15, // RFC 4253 6.1
+	})); err != nil {
+		c.chanlist.remove(ch.id)
+		return nil, err
+	}
+	if err := ch.waitForChannelOpenResponse(); err != nil {
+		c.chanlist.remove(ch.id)
+		return nil, fmt.Errorf("ssh: unable to open session: %v", err)
+	}
+	return &Session{
+		clientChan: ch,
+	}, nil
+}
+
+// An ExitError reports unsuccessful completion of a remote command.
+type ExitError struct {
+	Waitmsg
+}
+
+func (e *ExitError) Error() string {
+	return e.Waitmsg.String()
+}
+
+// Waitmsg stores the information about an exited remote command
+// as reported by Wait.
+type Waitmsg struct {
+	status int
+	signal string
+	msg    string
+	lang   string
+}
+
+// ExitStatus returns the exit status of the remote command.
+func (w Waitmsg) ExitStatus() int {
+	return w.status
+}
+
+// Signal returns the exit signal of the remote command if
+// it was terminated violently.
+func (w Waitmsg) Signal() string {
+	return w.signal
+}
+
+// Msg returns the exit message given by the remote command
+func (w Waitmsg) Msg() string {
+	return w.msg
+}
+
+// Lang returns the language tag. See RFC 3066
+func (w Waitmsg) Lang() string {
+	return w.lang
+}
+
+func (w Waitmsg) String() string {
+	return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
+}
diff --git a/ssh/session_test.go b/ssh/session_test.go
new file mode 100644
index 0000000..4a3d22b
--- /dev/null
+++ b/ssh/session_test.go
@@ -0,0 +1,374 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// Session tests.
+
+import (
+	"bytes"
+	"exp/terminal"
+	"io"
+	"testing"
+)
+
+type serverType func(*channel)
+
+// dial constructs a new test server and returns a *ClientConn.
+func dial(handler serverType, t *testing.T) *ClientConn {
+	pw := password("tiger")
+	serverConfig.PasswordCallback = func(user, pass string) bool {
+		return user == "testuser" && pass == string(pw)
+	}
+	serverConfig.PublicKeyCallback = nil
+
+	l, err := Listen("tcp", "127.0.0.1:0", serverConfig)
+	if err != nil {
+		t.Fatalf("unable to listen: %s", err)
+	}
+	go func() {
+		defer l.Close()
+		conn, err := l.Accept()
+		if err != nil {
+			t.Errorf("Unable to accept: %v", err)
+			return
+		}
+		defer conn.Close()
+		if err := conn.Handshake(); err != nil {
+			t.Errorf("Unable to handshake: %v", err)
+			return
+		}
+		for {
+			ch, err := conn.Accept()
+			if err == io.EOF {
+				return
+			}
+			if err != nil {
+				t.Errorf("Unable to accept incoming channel request: %v", err)
+				return
+			}
+			if ch.ChannelType() != "session" {
+				ch.Reject(UnknownChannelType, "unknown channel type")
+				continue
+			}
+			ch.Accept()
+			go handler(ch.(*channel))
+		}
+		t.Log("done")
+	}()
+
+	config := &ClientConfig{
+		User: "testuser",
+		Auth: []ClientAuth{
+			ClientAuthPassword(pw),
+		},
+	}
+
+	c, err := Dial("tcp", l.Addr().String(), config)
+	if err != nil {
+		t.Fatalf("unable to dial remote side: %s", err)
+	}
+	return c
+}
+
+// Test a simple string is returned to session.Stdout.
+func TestSessionShell(t *testing.T) {
+	conn := dial(shellHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	stdout := new(bytes.Buffer)
+	session.Stdout = stdout
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	if err := session.Wait(); err != nil {
+		t.Fatalf("Remote command did not exit cleanly: %s", err)
+	}
+	actual := stdout.String()
+	if actual != "golang" {
+		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+	}
+}
+
+// TODO(dfc) add support for Std{in,err}Pipe when the Server supports it.
+
+// Test a simple string is returned via StdoutPipe.
+func TestSessionStdoutPipe(t *testing.T) {
+	conn := dial(shellHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	stdout, err := session.StdoutPipe()
+	if err != nil {
+		t.Fatalf("Unable to request StdoutPipe(): %v", err)
+	}
+	var buf bytes.Buffer
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	done := make(chan bool, 1)
+	go func() {
+		if _, err := io.Copy(&buf, stdout); err != nil {
+			t.Errorf("Copy of stdout failed: %v", err)
+		}
+		done <- true
+	}()
+	if err := session.Wait(); err != nil {
+		t.Fatalf("Remote command did not exit cleanly: %s", err)
+	}
+	<-done
+	actual := buf.String()
+	if actual != "golang" {
+		t.Fatalf("Remote shell did not return expected string: expected=golang, actual=%s", actual)
+	}
+}
+
+// Test non-0 exit status is returned correctly.
+func TestExitStatusNonZero(t *testing.T) {
+	conn := dial(exitStatusNonZeroHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err == nil {
+		t.Fatalf("expected command to fail but it didn't")
+	}
+	e, ok := err.(*ExitError)
+	if !ok {
+		t.Fatalf("expected *ExitError but got %T", err)
+	}
+	if e.ExitStatus() != 15 {
+		t.Fatalf("expected command to exit with 15 but got %s", e.ExitStatus())
+	}
+}
+
+// Test 0 exit status is returned correctly.
+func TestExitStatusZero(t *testing.T) {
+	conn := dial(exitStatusZeroHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err != nil {
+		t.Fatalf("expected nil but got %s", err)
+	}
+}
+
+// Test exit signal and status are both returned correctly.
+func TestExitSignalAndStatus(t *testing.T) {
+	conn := dial(exitSignalAndStatusHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err == nil {
+		t.Fatalf("expected command to fail but it didn't")
+	}
+	e, ok := err.(*ExitError)
+	if !ok {
+		t.Fatalf("expected *ExitError but got %T", err)
+	}
+	if e.Signal() != "TERM" || e.ExitStatus() != 15 {
+		t.Fatalf("expected command to exit with signal TERM and status 15 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+	}
+}
+
+// Test exit signal and status are both returned correctly.
+func TestKnownExitSignalOnly(t *testing.T) {
+	conn := dial(exitSignalHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err == nil {
+		t.Fatalf("expected command to fail but it didn't")
+	}
+	e, ok := err.(*ExitError)
+	if !ok {
+		t.Fatalf("expected *ExitError but got %T", err)
+	}
+	if e.Signal() != "TERM" || e.ExitStatus() != 143 {
+		t.Fatalf("expected command to exit with signal TERM and status 143 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+	}
+}
+
+// Test exit signal and status are both returned correctly.
+func TestUnknownExitSignal(t *testing.T) {
+	conn := dial(exitSignalUnknownHandler, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err == nil {
+		t.Fatalf("expected command to fail but it didn't")
+	}
+	e, ok := err.(*ExitError)
+	if !ok {
+		t.Fatalf("expected *ExitError but got %T", err)
+	}
+	if e.Signal() != "SYS" || e.ExitStatus() != 128 {
+		t.Fatalf("expected command to exit with signal SYS and status 128 but got signal %s and status %v", e.Signal(), e.ExitStatus())
+	}
+}
+
+// Test WaitMsg is not returned if the channel closes abruptly.
+func TestExitWithoutStatusOrSignal(t *testing.T) {
+	conn := dial(exitWithoutSignalOrStatus, t)
+	defer conn.Close()
+	session, err := conn.NewSession()
+	if err != nil {
+		t.Fatalf("Unable to request new session: %s", err)
+	}
+	defer session.Close()
+	if err := session.Shell(); err != nil {
+		t.Fatalf("Unable to execute command: %s", err)
+	}
+	err = session.Wait()
+	if err == nil {
+		t.Fatalf("expected command to fail but it didn't")
+	}
+	_, ok := err.(*ExitError)
+	if ok {
+		// you can't actually test for errors.errorString
+		// because it's not exported.
+		t.Fatalf("expected *errorString but got %T", err)
+	}
+}
+
+type exitStatusMsg struct {
+	PeersId   uint32
+	Request   string
+	WantReply bool
+	Status    uint32
+}
+
+type exitSignalMsg struct {
+	PeersId    uint32
+	Request    string
+	WantReply  bool
+	Signal     string
+	CoreDumped bool
+	Errmsg     string
+	Lang       string
+}
+
+func newServerShell(ch *channel, prompt string) *ServerTerminal {
+	term := terminal.NewTerminal(ch, prompt)
+	return &ServerTerminal{
+		Term:    term,
+		Channel: ch,
+	}
+}
+
+func exitStatusZeroHandler(ch *channel) {
+	defer ch.Close()
+	// this string is returned to stdout
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+	sendStatus(0, ch)
+}
+
+func exitStatusNonZeroHandler(ch *channel) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+	sendStatus(15, ch)
+}
+
+func exitSignalAndStatusHandler(ch *channel) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+	sendStatus(15, ch)
+	sendSignal("TERM", ch)
+}
+
+func exitSignalHandler(ch *channel) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+	sendSignal("TERM", ch)
+}
+
+func exitSignalUnknownHandler(ch *channel) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+	sendSignal("SYS", ch)
+}
+
+func exitWithoutSignalOrStatus(ch *channel) {
+	defer ch.Close()
+	shell := newServerShell(ch, "> ")
+	shell.ReadLine()
+}
+
+func shellHandler(ch *channel) {
+	defer ch.Close()
+	// this string is returned to stdout
+	shell := newServerShell(ch, "golang")
+	shell.ReadLine()
+	sendStatus(0, ch)
+}
+
+func sendStatus(status uint32, ch *channel) {
+	msg := exitStatusMsg{
+		PeersId:   ch.theirId,
+		Request:   "exit-status",
+		WantReply: false,
+		Status:    status,
+	}
+	ch.serverConn.writePacket(marshal(msgChannelRequest, msg))
+}
+
+func sendSignal(signal string, ch *channel) {
+	sig := exitSignalMsg{
+		PeersId:    ch.theirId,
+		Request:    "exit-signal",
+		WantReply:  false,
+		Signal:     signal,
+		CoreDumped: false,
+		Errmsg:     "Process terminated",
+		Lang:       "en-GB-oed",
+	}
+	ch.serverConn.writePacket(marshal(msgChannelRequest, sig))
+}
diff --git a/ssh/tcpip.go b/ssh/tcpip.go
new file mode 100644
index 0000000..e0c47bc
--- /dev/null
+++ b/ssh/tcpip.go
@@ -0,0 +1,132 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"time"
+)
+
+// Dial initiates a connection to the addr from the remote host.
+// addr is resolved using net.ResolveTCPAddr before connection. 
+// This could allow an observer to observe the DNS name of the 
+// remote host. Consider using ssh.DialTCP to avoid this.
+func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
+	raddr, err := net.ResolveTCPAddr(n, addr)
+	if err != nil {
+		return nil, err
+	}
+	return c.DialTCP(n, nil, raddr)
+}
+
+// DialTCP connects to the remote address raddr on the network net,
+// which must be "tcp", "tcp4", or "tcp6".  If laddr is not nil, it is used
+// as the local address for the connection.
+func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
+	if laddr == nil {
+		laddr = &net.TCPAddr{
+			IP:   net.IPv4zero,
+			Port: 0,
+		}
+	}
+	ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
+	if err != nil {
+		return nil, err
+	}
+	return &tcpchanconn{
+		tcpchan: ch,
+		laddr:   laddr,
+		raddr:   raddr,
+	}, nil
+}
+
+// RFC 4254 7.2
+type channelOpenDirectMsg struct {
+	ChanType      string
+	PeersId       uint32
+	PeersWindow   uint32
+	MaxPacketSize uint32
+	raddr         string
+	rport         uint32
+	laddr         string
+	lport         uint32
+}
+
+// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
+// strings and are expected to be resolveable at the remote end.
+func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpchan, error) {
+	ch := c.newChan(c.transport)
+	if err := c.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
+		ChanType:      "direct-tcpip",
+		PeersId:       ch.id,
+		PeersWindow:   1 << 14,
+		MaxPacketSize: 1 << 15, // RFC 4253 6.1
+		raddr:         raddr,
+		rport:         uint32(rport),
+		laddr:         laddr,
+		lport:         uint32(lport),
+	})); err != nil {
+		c.chanlist.remove(ch.id)
+		return nil, err
+	}
+	if err := ch.waitForChannelOpenResponse(); err != nil {
+		c.chanlist.remove(ch.id)
+		return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
+	}
+	return &tcpchan{
+		clientChan: ch,
+		Reader:     ch.stdout,
+		Writer:     ch.stdin,
+	}, nil
+}
+
+type tcpchan struct {
+	*clientChan // the backing channel
+	io.Reader
+	io.Writer
+}
+
+// tcpchanconn fulfills the net.Conn interface without 
+// the tcpchan having to hold laddr or raddr directly.
+type tcpchanconn struct {
+	*tcpchan
+	laddr, raddr net.Addr
+}
+
+// LocalAddr returns the local network address.
+func (t *tcpchanconn) LocalAddr() net.Addr {
+	return t.laddr
+}
+
+// RemoteAddr returns the remote network address.
+func (t *tcpchanconn) RemoteAddr() net.Addr {
+	return t.raddr
+}
+
+// SetDeadline sets the read and write deadlines associated
+// with the connection.
+func (t *tcpchanconn) SetDeadline(deadline time.Time) error {
+	if err := t.SetReadDeadline(deadline); err != nil {
+		return err
+	}
+	return t.SetWriteDeadline(deadline)
+}
+
+// SetReadDeadline sets the read deadline.
+// A zero value for t means Read will not time out.
+// After the deadline, the error from Read will implement net.Error
+// with Timeout() == true.
+func (t *tcpchanconn) SetReadDeadline(deadline time.Time) error {
+	return errors.New("ssh: tcpchan: deadline not supported")
+}
+
+// SetWriteDeadline exists to satisfy the net.Conn interface
+// but is not implemented by this type.  It always returns an error.
+func (t *tcpchanconn) SetWriteDeadline(deadline time.Time) error {
+	return errors.New("ssh: tcpchan: deadline not supported")
+}
diff --git a/ssh/tcpip_func_test.go b/ssh/tcpip_func_test.go
new file mode 100644
index 0000000..2612972
--- /dev/null
+++ b/ssh/tcpip_func_test.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+// direct-tcpip functional tests
+
+import (
+	"net"
+	"net/http"
+	"testing"
+)
+
+func TestTCPIPHTTP(t *testing.T) {
+	if *sshuser == "" {
+		t.Log("ssh.user not defined, skipping test")
+		return
+	}
+	// google.com will generate at least one redirect, possibly three
+	// depending on your location.
+	doTest(t, "http://google.com")
+}
+
+func TestTCPIPHTTPS(t *testing.T) {
+	if *sshuser == "" {
+		t.Log("ssh.user not defined, skipping test")
+		return
+	}
+	doTest(t, "https://encrypted.google.com/")
+}
+
+func doTest(t *testing.T, url string) {
+	config := &ClientConfig{
+		User: *sshuser,
+		Auth: []ClientAuth{
+			ClientAuthPassword(password(*sshpass)),
+		},
+	}
+	conn, err := Dial("tcp", "localhost:22", config)
+	if err != nil {
+		t.Fatalf("Unable to connect: %s", err)
+	}
+	defer conn.Close()
+	tr := &http.Transport{
+		Dial: func(n, addr string) (net.Conn, error) {
+			return conn.Dial(n, addr)
+		},
+	}
+	client := &http.Client{
+		Transport: tr,
+	}
+	resp, err := client.Get(url)
+	if err != nil {
+		t.Fatalf("unable to proxy: %s", err)
+	}
+	// got a body without error
+	t.Log(resp)
+}
diff --git a/ssh/transport.go b/ssh/transport.go
new file mode 100644
index 0000000..e21bc4b
--- /dev/null
+++ b/ssh/transport.go
@@ -0,0 +1,369 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bufio"
+	"crypto"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/sha1"
+	"crypto/subtle"
+	"errors"
+	"hash"
+	"io"
+	"net"
+	"sync"
+)
+
+const (
+	packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
+	minPacketSize      = 16
+	maxPacketSize      = 36000
+	minPaddingSize     = 4 // TODO(huin) should this be configurable?
+)
+
+// filteredConn reduces the set of methods exposed when embeddeding
+// a net.Conn inside ssh.transport.
+// TODO(dfc) suggestions for a better name will be warmly received.
+type filteredConn interface {
+	// Close closes the connection.
+	Close() error
+
+	// LocalAddr returns the local network address.
+	LocalAddr() net.Addr
+
+	// RemoteAddr returns the remote network address.
+	RemoteAddr() net.Addr
+}
+
+// Types implementing packetWriter provide the ability to send packets to
+// an SSH peer.
+type packetWriter interface {
+	// Encrypt and send a packet of data to the remote peer.
+	writePacket(packet []byte) error
+}
+
+// transport represents the SSH connection to the remote peer.
+type transport struct {
+	reader
+	writer
+
+	filteredConn
+}
+
+// reader represents the incoming connection state.
+type reader struct {
+	io.Reader
+	common
+}
+
+// writer represnts the outgoing connection state.
+type writer struct {
+	*sync.Mutex // protects writer.Writer from concurrent writes
+	*bufio.Writer
+	rand io.Reader
+	common
+}
+
+// common represents the cipher state needed to process messages in a single
+// direction.
+type common struct {
+	seqNum uint32
+	mac    hash.Hash
+	cipher cipher.Stream
+
+	cipherAlgo      string
+	macAlgo         string
+	compressionAlgo string
+}
+
+// Read and decrypt a single packet from the remote peer.
+func (r *reader) readOnePacket() ([]byte, error) {
+	var lengthBytes = make([]byte, 5)
+	var macSize uint32
+	if _, err := io.ReadFull(r, lengthBytes); err != nil {
+		return nil, err
+	}
+
+	r.cipher.XORKeyStream(lengthBytes, lengthBytes)
+
+	if r.mac != nil {
+		r.mac.Reset()
+		seqNumBytes := []byte{
+			byte(r.seqNum >> 24),
+			byte(r.seqNum >> 16),
+			byte(r.seqNum >> 8),
+			byte(r.seqNum),
+		}
+		r.mac.Write(seqNumBytes)
+		r.mac.Write(lengthBytes)
+		macSize = uint32(r.mac.Size())
+	}
+
+	length := uint32(lengthBytes[0])<<24 | uint32(lengthBytes[1])<<16 | uint32(lengthBytes[2])<<8 | uint32(lengthBytes[3])
+	paddingLength := uint32(lengthBytes[4])
+
+	if length <= paddingLength+1 {
+		return nil, errors.New("invalid packet length")
+	}
+	if length > maxPacketSize {
+		return nil, errors.New("packet too large")
+	}
+
+	packet := make([]byte, length-1+macSize)
+	if _, err := io.ReadFull(r, packet); err != nil {
+		return nil, err
+	}
+	mac := packet[length-1:]
+	r.cipher.XORKeyStream(packet, packet[:length-1])
+
+	if r.mac != nil {
+		r.mac.Write(packet[:length-1])
+		if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
+			return nil, errors.New("ssh: MAC failure")
+		}
+	}
+
+	r.seqNum++
+	return packet[:length-paddingLength-1], nil
+}
+
+// Read and decrypt next packet discarding debug and noop messages.
+func (t *transport) readPacket() ([]byte, error) {
+	for {
+		packet, err := t.readOnePacket()
+		if err != nil {
+			return nil, err
+		}
+		if packet[0] != msgIgnore && packet[0] != msgDebug {
+			return packet, nil
+		}
+	}
+	panic("unreachable")
+}
+
+// Encrypt and send a packet of data to the remote peer.
+func (w *writer) writePacket(packet []byte) error {
+	w.Mutex.Lock()
+	defer w.Mutex.Unlock()
+
+	paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
+	if paddingLength < 4 {
+		paddingLength += packetSizeMultiple
+	}
+
+	length := len(packet) + 1 + paddingLength
+	lengthBytes := []byte{
+		byte(length >> 24),
+		byte(length >> 16),
+		byte(length >> 8),
+		byte(length),
+		byte(paddingLength),
+	}
+	padding := make([]byte, paddingLength)
+	_, err := io.ReadFull(w.rand, padding)
+	if err != nil {
+		return err
+	}
+
+	if w.mac != nil {
+		w.mac.Reset()
+		seqNumBytes := []byte{
+			byte(w.seqNum >> 24),
+			byte(w.seqNum >> 16),
+			byte(w.seqNum >> 8),
+			byte(w.seqNum),
+		}
+		w.mac.Write(seqNumBytes)
+		w.mac.Write(lengthBytes)
+		w.mac.Write(packet)
+		w.mac.Write(padding)
+	}
+
+	// TODO(dfc) lengthBytes, packet and padding should be
+	// subslices of a single buffer
+	w.cipher.XORKeyStream(lengthBytes, lengthBytes)
+	w.cipher.XORKeyStream(packet, packet)
+	w.cipher.XORKeyStream(padding, padding)
+
+	if _, err := w.Write(lengthBytes); err != nil {
+		return err
+	}
+	if _, err := w.Write(packet); err != nil {
+		return err
+	}
+	if _, err := w.Write(padding); err != nil {
+		return err
+	}
+
+	if w.mac != nil {
+		if _, err := w.Write(w.mac.Sum(nil)); err != nil {
+			return err
+		}
+	}
+
+	if err := w.Flush(); err != nil {
+		return err
+	}
+	w.seqNum++
+	return err
+}
+
+// Send a message to the remote peer
+func (t *transport) sendMessage(typ uint8, msg interface{}) error {
+	packet := marshal(typ, msg)
+	return t.writePacket(packet)
+}
+
+func newTransport(conn net.Conn, rand io.Reader) *transport {
+	return &transport{
+		reader: reader{
+			Reader: bufio.NewReader(conn),
+			common: common{
+				cipher: noneCipher{},
+			},
+		},
+		writer: writer{
+			Writer: bufio.NewWriter(conn),
+			rand:   rand,
+			Mutex:  new(sync.Mutex),
+			common: common{
+				cipher: noneCipher{},
+			},
+		},
+		filteredConn: conn,
+	}
+}
+
+type direction struct {
+	ivTag     []byte
+	keyTag    []byte
+	macKeyTag []byte
+}
+
+// TODO(dfc) can this be made a constant ?
+var (
+	serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
+	clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
+)
+
+// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
+// described in RFC 4253, section 6.4. direction should either be serverKeys
+// (to setup server->client keys) or clientKeys (for client->server keys).
+func (c *common) setupKeys(d direction, K, H, sessionId []byte, hashFunc crypto.Hash) error {
+	cipherMode := cipherModes[c.cipherAlgo]
+
+	macKeySize := 20
+
+	iv := make([]byte, cipherMode.ivSize)
+	key := make([]byte, cipherMode.keySize)
+	macKey := make([]byte, macKeySize)
+
+	h := hashFunc.New()
+	generateKeyMaterial(iv, d.ivTag, K, H, sessionId, h)
+	generateKeyMaterial(key, d.keyTag, K, H, sessionId, h)
+	generateKeyMaterial(macKey, d.macKeyTag, K, H, sessionId, h)
+
+	c.mac = truncatingMAC{12, hmac.New(sha1.New, macKey)}
+
+	cipher, err := cipherMode.createCipher(key, iv)
+	if err != nil {
+		return err
+	}
+
+	c.cipher = cipher
+
+	return nil
+}
+
+// generateKeyMaterial fills out with key material generated from tag, K, H
+// and sessionId, as specified in RFC 4253, section 7.2.
+func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
+	var digestsSoFar []byte
+
+	for len(out) > 0 {
+		h.Reset()
+		h.Write(K)
+		h.Write(H)
+
+		if len(digestsSoFar) == 0 {
+			h.Write(tag)
+			h.Write(sessionId)
+		} else {
+			h.Write(digestsSoFar)
+		}
+
+		digest := h.Sum(nil)
+		n := copy(out, digest)
+		out = out[n:]
+		if len(out) > 0 {
+			digestsSoFar = append(digestsSoFar, digest...)
+		}
+	}
+}
+
+// truncatingMAC wraps around a hash.Hash and truncates the output digest to
+// a given size.
+type truncatingMAC struct {
+	length int
+	hmac   hash.Hash
+}
+
+func (t truncatingMAC) Write(data []byte) (int, error) {
+	return t.hmac.Write(data)
+}
+
+func (t truncatingMAC) Sum(in []byte) []byte {
+	out := t.hmac.Sum(in)
+	return out[:len(in)+t.length]
+}
+
+func (t truncatingMAC) Reset() {
+	t.hmac.Reset()
+}
+
+func (t truncatingMAC) Size() int {
+	return t.length
+}
+
+func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
+
+// maxVersionStringBytes is the maximum number of bytes that we'll accept as a
+// version string. In the event that the client is talking a different protocol
+// we need to set a limit otherwise we will keep using more and more memory
+// while searching for the end of the version handshake.
+const maxVersionStringBytes = 1024
+
+// Read version string as specified by RFC 4253, section 4.2.
+func readVersion(r io.Reader) ([]byte, error) {
+	versionString := make([]byte, 0, 64)
+	var ok bool
+	var buf [1]byte
+forEachByte:
+	for len(versionString) < maxVersionStringBytes {
+		_, err := io.ReadFull(r, buf[:])
+		if err != nil {
+			return nil, err
+		}
+		// The RFC says that the version should be terminated with \r\n
+		// but several SSH servers actually only send a \n.
+		if buf[0] == '\n' {
+			ok = true
+			break forEachByte
+		}
+		versionString = append(versionString, buf[0])
+	}
+
+	if !ok {
+		return nil, errors.New("ssh: failed to read version string")
+	}
+
+	// There might be a '\r' on the end which we should remove.
+	if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
+		versionString = versionString[:len(versionString)-1]
+	}
+	return versionString, nil
+}
diff --git a/ssh/transport_test.go b/ssh/transport_test.go
new file mode 100644
index 0000000..ab9177f
--- /dev/null
+++ b/ssh/transport_test.go
@@ -0,0 +1,51 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssh
+
+import (
+	"bufio"
+	"bytes"
+	"testing"
+)
+
+func TestReadVersion(t *testing.T) {
+	buf := serverVersion
+	result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
+	if err != nil {
+		t.Errorf("readVersion didn't read version correctly: %s", err)
+	}
+	if !bytes.Equal(buf[:len(buf)-2], result) {
+		t.Error("version read did not match expected")
+	}
+}
+
+func TestReadVersionWithJustLF(t *testing.T) {
+	var buf []byte
+	buf = append(buf, serverVersion...)
+	buf = buf[:len(buf)-1]
+	buf[len(buf)-1] = '\n'
+	result, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf)))
+	if err != nil {
+		t.Error("readVersion failed to handle just a \n")
+	}
+	if !bytes.Equal(buf[:len(buf)-1], result) {
+		t.Errorf("version read did not match expected: got %x, want %x", result, buf[:len(buf)-1])
+	}
+}
+
+func TestReadVersionTooLong(t *testing.T) {
+	buf := make([]byte, maxVersionStringBytes+1)
+	if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
+		t.Errorf("readVersion consumed %d bytes without error", len(buf))
+	}
+}
+
+func TestReadVersionWithoutCRLF(t *testing.T) {
+	buf := serverVersion
+	buf = buf[:len(buf)-1]
+	if _, err := readVersion(bufio.NewReader(bytes.NewBuffer(buf))); err == nil {
+		t.Error("readVersion did not notice \\n was missing")
+	}
+}
diff --git a/twofish/twofish.go b/twofish/twofish.go
new file mode 100644
index 0000000..0616e7b
--- /dev/null
+++ b/twofish/twofish.go
@@ -0,0 +1,355 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package twofish implements Bruce Schneier's Twofish encryption algorithm.
+package twofish
+
+// Twofish is defined in http://www.schneier.com/paper-twofish-paper.pdf [TWOFISH]
+
+// This code is a port of the LibTom C implementation.
+// See http://libtom.org/?page=features&newsitems=5&whatfile=crypt.
+// LibTomCrypt is free for all purposes under the public domain.
+// It was heavily inspired by the go blowfish package.
+
+import "strconv"
+
+// BlockSize is the constant block size of Twofish.
+const BlockSize = 16
+
+const mdsPolynomial = 0x169 // x^8 + x^6 + x^5 + x^3 + 1, see [TWOFISH] 4.2
+const rsPolynomial = 0x14d  // x^8 + x^6 + x^3 + x^2 + 1, see [TWOFISH] 4.3
+
+// A Cipher is an instance of Twofish encryption using a particular key.
+type Cipher struct {
+	s [4][256]uint32
+	k [40]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+	return "crypto/twofish: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a Cipher.
+// The key argument should be the Twofish key, 16, 24 or 32 bytes.
+func NewCipher(key []byte) (*Cipher, error) {
+	keylen := len(key)
+
+	if keylen != 16 && keylen != 24 && keylen != 32 {
+		return nil, KeySizeError(keylen)
+	}
+
+	// k is the number of 64 bit words in key
+	k := keylen / 8
+
+	// Create the S[..] words
+	var S [4 * 4]byte
+	for i := 0; i < k; i++ {
+		// Computes [y0 y1 y2 y3] = rs . [x0 x1 x2 x3 x4 x5 x6 x7]
+		for j, rsRow := range rs {
+			for k, rsVal := range rsRow {
+				S[4*i+j] ^= gfMult(key[8*i+k], rsVal, rsPolynomial)
+			}
+		}
+	}
+
+	// Calculate subkeys
+	c := new(Cipher)
+	var tmp [4]byte
+	for i := byte(0); i < 20; i++ {
+		// A = h(p * 2x, Me)
+		for j := range tmp {
+			tmp[j] = 2 * i
+		}
+		A := h(tmp[:], key, 0)
+
+		// B = rolc(h(p * (2x + 1), Mo), 8)
+		for j := range tmp {
+			tmp[j] = 2*i + 1
+		}
+		B := h(tmp[:], key, 1)
+		B = rol(B, 8)
+
+		c.k[2*i] = A + B
+
+		// K[2i+1] = (A + 2B) <<< 9
+		c.k[2*i+1] = rol(2*B+A, 9)
+	}
+
+	// Calculate sboxes
+	switch k {
+	case 2:
+		for i := range c.s[0] {
+			c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][byte(i)]^S[0]]^S[4]], 0)
+			c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][byte(i)]^S[1]]^S[5]], 1)
+			c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][byte(i)]^S[2]]^S[6]], 2)
+			c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][byte(i)]^S[3]]^S[7]], 3)
+		}
+	case 3:
+		for i := range c.s[0] {
+			c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]], 0)
+			c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[1]]^S[5]]^S[9]], 1)
+			c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]], 2)
+			c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[3]]^S[7]]^S[11]], 3)
+		}
+	default:
+		for i := range c.s[0] {
+			c.s[0][i] = mdsColumnMult(sbox[1][sbox[0][sbox[0][sbox[1][sbox[1][byte(i)]^S[0]]^S[4]]^S[8]]^S[12]], 0)
+			c.s[1][i] = mdsColumnMult(sbox[0][sbox[0][sbox[1][sbox[1][sbox[0][byte(i)]^S[1]]^S[5]]^S[9]]^S[13]], 1)
+			c.s[2][i] = mdsColumnMult(sbox[1][sbox[1][sbox[0][sbox[0][sbox[0][byte(i)]^S[2]]^S[6]]^S[10]]^S[14]], 2)
+			c.s[3][i] = mdsColumnMult(sbox[0][sbox[1][sbox[1][sbox[0][sbox[1][byte(i)]^S[3]]^S[7]]^S[11]]^S[15]], 3)
+		}
+	}
+
+	return c, nil
+}
+
+// Reset zeros the key data, so that it will no longer appear in the process's
+// memory.
+func (c *Cipher) Reset() {
+	for i := range c.k {
+		c.k[i] = 0
+	}
+	for i := range c.s {
+		for j := 0; j < 256; j++ {
+			c.s[i][j] = 0
+		}
+	}
+}
+
+// BlockSize returns the Twofish block size, 16 bytes.
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// store32l stores src in dst in little-endian form.
+func store32l(dst []byte, src uint32) {
+	dst[0] = byte(src)
+	dst[1] = byte(src >> 8)
+	dst[2] = byte(src >> 16)
+	dst[3] = byte(src >> 24)
+	return
+}
+
+// load32l reads a little-endian uint32 from src.
+func load32l(src []byte) uint32 {
+	return uint32(src[0]) | uint32(src[1])<<8 | uint32(src[2])<<16 | uint32(src[3])<<24
+}
+
+// rol returns x after a left circular rotation of y bits.
+func rol(x, y uint32) uint32 {
+	return (x << (y & 31)) | (x >> (32 - (y & 31)))
+}
+
+// ror returns x after a right circular rotation of y bits.
+func ror(x, y uint32) uint32 {
+	return (x >> (y & 31)) | (x << (32 - (y & 31)))
+}
+
+// The RS matrix. See [TWOFISH] 4.3
+var rs = [4][8]byte{
+	{0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E},
+	{0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5},
+	{0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19},
+	{0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03},
+}
+
+// sbox tables
+var sbox = [2][256]byte{
+	{
+		0xa9, 0x67, 0xb3, 0xe8, 0x04, 0xfd, 0xa3, 0x76, 0x9a, 0x92, 0x80, 0x78, 0xe4, 0xdd, 0xd1, 0x38,
+		0x0d, 0xc6, 0x35, 0x98, 0x18, 0xf7, 0xec, 0x6c, 0x43, 0x75, 0x37, 0x26, 0xfa, 0x13, 0x94, 0x48,
+		0xf2, 0xd0, 0x8b, 0x30, 0x84, 0x54, 0xdf, 0x23, 0x19, 0x5b, 0x3d, 0x59, 0xf3, 0xae, 0xa2, 0x82,
+		0x63, 0x01, 0x83, 0x2e, 0xd9, 0x51, 0x9b, 0x7c, 0xa6, 0xeb, 0xa5, 0xbe, 0x16, 0x0c, 0xe3, 0x61,
+		0xc0, 0x8c, 0x3a, 0xf5, 0x73, 0x2c, 0x25, 0x0b, 0xbb, 0x4e, 0x89, 0x6b, 0x53, 0x6a, 0xb4, 0xf1,
+		0xe1, 0xe6, 0xbd, 0x45, 0xe2, 0xf4, 0xb6, 0x66, 0xcc, 0x95, 0x03, 0x56, 0xd4, 0x1c, 0x1e, 0xd7,
+		0xfb, 0xc3, 0x8e, 0xb5, 0xe9, 0xcf, 0xbf, 0xba, 0xea, 0x77, 0x39, 0xaf, 0x33, 0xc9, 0x62, 0x71,
+		0x81, 0x79, 0x09, 0xad, 0x24, 0xcd, 0xf9, 0xd8, 0xe5, 0xc5, 0xb9, 0x4d, 0x44, 0x08, 0x86, 0xe7,
+		0xa1, 0x1d, 0xaa, 0xed, 0x06, 0x70, 0xb2, 0xd2, 0x41, 0x7b, 0xa0, 0x11, 0x31, 0xc2, 0x27, 0x90,
+		0x20, 0xf6, 0x60, 0xff, 0x96, 0x5c, 0xb1, 0xab, 0x9e, 0x9c, 0x52, 0x1b, 0x5f, 0x93, 0x0a, 0xef,
+		0x91, 0x85, 0x49, 0xee, 0x2d, 0x4f, 0x8f, 0x3b, 0x47, 0x87, 0x6d, 0x46, 0xd6, 0x3e, 0x69, 0x64,
+		0x2a, 0xce, 0xcb, 0x2f, 0xfc, 0x97, 0x05, 0x7a, 0xac, 0x7f, 0xd5, 0x1a, 0x4b, 0x0e, 0xa7, 0x5a,
+		0x28, 0x14, 0x3f, 0x29, 0x88, 0x3c, 0x4c, 0x02, 0xb8, 0xda, 0xb0, 0x17, 0x55, 0x1f, 0x8a, 0x7d,
+		0x57, 0xc7, 0x8d, 0x74, 0xb7, 0xc4, 0x9f, 0x72, 0x7e, 0x15, 0x22, 0x12, 0x58, 0x07, 0x99, 0x34,
+		0x6e, 0x50, 0xde, 0x68, 0x65, 0xbc, 0xdb, 0xf8, 0xc8, 0xa8, 0x2b, 0x40, 0xdc, 0xfe, 0x32, 0xa4,
+		0xca, 0x10, 0x21, 0xf0, 0xd3, 0x5d, 0x0f, 0x00, 0x6f, 0x9d, 0x36, 0x42, 0x4a, 0x5e, 0xc1, 0xe0,
+	},
+	{
+		0x75, 0xf3, 0xc6, 0xf4, 0xdb, 0x7b, 0xfb, 0xc8, 0x4a, 0xd3, 0xe6, 0x6b, 0x45, 0x7d, 0xe8, 0x4b,
+		0xd6, 0x32, 0xd8, 0xfd, 0x37, 0x71, 0xf1, 0xe1, 0x30, 0x0f, 0xf8, 0x1b, 0x87, 0xfa, 0x06, 0x3f,
+		0x5e, 0xba, 0xae, 0x5b, 0x8a, 0x00, 0xbc, 0x9d, 0x6d, 0xc1, 0xb1, 0x0e, 0x80, 0x5d, 0xd2, 0xd5,
+		0xa0, 0x84, 0x07, 0x14, 0xb5, 0x90, 0x2c, 0xa3, 0xb2, 0x73, 0x4c, 0x54, 0x92, 0x74, 0x36, 0x51,
+		0x38, 0xb0, 0xbd, 0x5a, 0xfc, 0x60, 0x62, 0x96, 0x6c, 0x42, 0xf7, 0x10, 0x7c, 0x28, 0x27, 0x8c,
+		0x13, 0x95, 0x9c, 0xc7, 0x24, 0x46, 0x3b, 0x70, 0xca, 0xe3, 0x85, 0xcb, 0x11, 0xd0, 0x93, 0xb8,
+		0xa6, 0x83, 0x20, 0xff, 0x9f, 0x77, 0xc3, 0xcc, 0x03, 0x6f, 0x08, 0xbf, 0x40, 0xe7, 0x2b, 0xe2,
+		0x79, 0x0c, 0xaa, 0x82, 0x41, 0x3a, 0xea, 0xb9, 0xe4, 0x9a, 0xa4, 0x97, 0x7e, 0xda, 0x7a, 0x17,
+		0x66, 0x94, 0xa1, 0x1d, 0x3d, 0xf0, 0xde, 0xb3, 0x0b, 0x72, 0xa7, 0x1c, 0xef, 0xd1, 0x53, 0x3e,
+		0x8f, 0x33, 0x26, 0x5f, 0xec, 0x76, 0x2a, 0x49, 0x81, 0x88, 0xee, 0x21, 0xc4, 0x1a, 0xeb, 0xd9,
+		0xc5, 0x39, 0x99, 0xcd, 0xad, 0x31, 0x8b, 0x01, 0x18, 0x23, 0xdd, 0x1f, 0x4e, 0x2d, 0xf9, 0x48,
+		0x4f, 0xf2, 0x65, 0x8e, 0x78, 0x5c, 0x58, 0x19, 0x8d, 0xe5, 0x98, 0x57, 0x67, 0x7f, 0x05, 0x64,
+		0xaf, 0x63, 0xb6, 0xfe, 0xf5, 0xb7, 0x3c, 0xa5, 0xce, 0xe9, 0x68, 0x44, 0xe0, 0x4d, 0x43, 0x69,
+		0x29, 0x2e, 0xac, 0x15, 0x59, 0xa8, 0x0a, 0x9e, 0x6e, 0x47, 0xdf, 0x34, 0x35, 0x6a, 0xcf, 0xdc,
+		0x22, 0xc9, 0xc0, 0x9b, 0x89, 0xd4, 0xed, 0xab, 0x12, 0xa2, 0x0d, 0x52, 0xbb, 0x02, 0x2f, 0xa9,
+		0xd7, 0x61, 0x1e, 0xb4, 0x50, 0x04, 0xf6, 0xc2, 0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xbe, 0x91,
+	},
+}
+
+// gfMult returns a·b in GF(2^8)/p
+func gfMult(a, b byte, p uint32) byte {
+	B := [2]uint32{0, uint32(b)}
+	P := [2]uint32{0, p}
+	var result uint32
+
+	// branchless GF multiplier
+	for i := 0; i < 7; i++ {
+		result ^= B[a&1]
+		a >>= 1
+		B[1] = P[B[1]>>7] ^ (B[1] << 1)
+	}
+	result ^= B[a&1]
+	return byte(result)
+}
+
+// mdsColumnMult calculates y{col} where [y0 y1 y2 y3] = MDS · [x0]
+func mdsColumnMult(in byte, col int) uint32 {
+	mul01 := in
+	mul5B := gfMult(in, 0x5B, mdsPolynomial)
+	mulEF := gfMult(in, 0xEF, mdsPolynomial)
+
+	switch col {
+	case 0:
+		return uint32(mul01) | uint32(mul5B)<<8 | uint32(mulEF)<<16 | uint32(mulEF)<<24
+	case 1:
+		return uint32(mulEF) | uint32(mulEF)<<8 | uint32(mul5B)<<16 | uint32(mul01)<<24
+	case 2:
+		return uint32(mul5B) | uint32(mulEF)<<8 | uint32(mul01)<<16 | uint32(mulEF)<<24
+	case 3:
+		return uint32(mul5B) | uint32(mul01)<<8 | uint32(mulEF)<<16 | uint32(mul5B)<<24
+	}
+
+	panic("unreachable")
+}
+
+// h implements the S-box generation function. See [TWOFISH] 4.3.5
+func h(in, key []byte, offset int) uint32 {
+	var y [4]byte
+	for x := range y {
+		y[x] = in[x]
+	}
+	switch len(key) / 8 {
+	case 4:
+		y[0] = sbox[1][y[0]] ^ key[4*(6+offset)+0]
+		y[1] = sbox[0][y[1]] ^ key[4*(6+offset)+1]
+		y[2] = sbox[0][y[2]] ^ key[4*(6+offset)+2]
+		y[3] = sbox[1][y[3]] ^ key[4*(6+offset)+3]
+		fallthrough
+	case 3:
+		y[0] = sbox[1][y[0]] ^ key[4*(4+offset)+0]
+		y[1] = sbox[1][y[1]] ^ key[4*(4+offset)+1]
+		y[2] = sbox[0][y[2]] ^ key[4*(4+offset)+2]
+		y[3] = sbox[0][y[3]] ^ key[4*(4+offset)+3]
+		fallthrough
+	case 2:
+		y[0] = sbox[1][sbox[0][sbox[0][y[0]]^key[4*(2+offset)+0]]^key[4*(0+offset)+0]]
+		y[1] = sbox[0][sbox[0][sbox[1][y[1]]^key[4*(2+offset)+1]]^key[4*(0+offset)+1]]
+		y[2] = sbox[1][sbox[1][sbox[0][y[2]]^key[4*(2+offset)+2]]^key[4*(0+offset)+2]]
+		y[3] = sbox[0][sbox[1][sbox[1][y[3]]^key[4*(2+offset)+3]]^key[4*(0+offset)+3]]
+	}
+	// [y0 y1 y2 y3] = MDS . [x0 x1 x2 x3]
+	var mdsMult uint32
+	for i := range y {
+		mdsMult ^= mdsColumnMult(y[i], i)
+	}
+	return mdsMult
+}
+
+// Encrypt encrypts a 16-byte block from src to dst, which may overlap.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) {
+	S1 := c.s[0]
+	S2 := c.s[1]
+	S3 := c.s[2]
+	S4 := c.s[3]
+
+	// Load input
+	ia := load32l(src[0:4])
+	ib := load32l(src[4:8])
+	ic := load32l(src[8:12])
+	id := load32l(src[12:16])
+
+	// Pre-whitening
+	ia ^= c.k[0]
+	ib ^= c.k[1]
+	ic ^= c.k[2]
+	id ^= c.k[3]
+
+	for i := 0; i < 8; i++ {
+		k := c.k[8+i*4 : 12+i*4]
+		t2 := S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)]
+		t1 := S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2
+		ic = ror(ic^(t1+k[0]), 1)
+		id = rol(id, 1) ^ (t2 + t1 + k[1])
+
+		t2 = S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)]
+		t1 = S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2
+		ia = ror(ia^(t1+k[2]), 1)
+		ib = rol(ib, 1) ^ (t2 + t1 + k[3])
+	}
+
+	// Output with "undo last swap"
+	ta := ic ^ c.k[4]
+	tb := id ^ c.k[5]
+	tc := ia ^ c.k[6]
+	td := ib ^ c.k[7]
+
+	store32l(dst[0:4], ta)
+	store32l(dst[4:8], tb)
+	store32l(dst[8:12], tc)
+	store32l(dst[12:16], td)
+}
+
+// Decrypt decrypts a 16-byte block from src to dst, which may overlap.
+func (c *Cipher) Decrypt(dst, src []byte) {
+	S1 := c.s[0]
+	S2 := c.s[1]
+	S3 := c.s[2]
+	S4 := c.s[3]
+
+	// Load input
+	ta := load32l(src[0:4])
+	tb := load32l(src[4:8])
+	tc := load32l(src[8:12])
+	td := load32l(src[12:16])
+
+	// Undo undo final swap
+	ia := tc ^ c.k[6]
+	ib := td ^ c.k[7]
+	ic := ta ^ c.k[4]
+	id := tb ^ c.k[5]
+
+	for i := 8; i > 0; i-- {
+		k := c.k[4+i*4 : 8+i*4]
+		t2 := S2[byte(id)] ^ S3[byte(id>>8)] ^ S4[byte(id>>16)] ^ S1[byte(id>>24)]
+		t1 := S1[byte(ic)] ^ S2[byte(ic>>8)] ^ S3[byte(ic>>16)] ^ S4[byte(ic>>24)] + t2
+		ia = rol(ia, 1) ^ (t1 + k[2])
+		ib = ror(ib^(t2+t1+k[3]), 1)
+
+		t2 = S2[byte(ib)] ^ S3[byte(ib>>8)] ^ S4[byte(ib>>16)] ^ S1[byte(ib>>24)]
+		t1 = S1[byte(ia)] ^ S2[byte(ia>>8)] ^ S3[byte(ia>>16)] ^ S4[byte(ia>>24)] + t2
+		ic = rol(ic, 1) ^ (t1 + k[0])
+		id = ror(id^(t2+t1+k[1]), 1)
+	}
+
+	// Undo pre-whitening
+	ia ^= c.k[0]
+	ib ^= c.k[1]
+	ic ^= c.k[2]
+	id ^= c.k[3]
+
+	store32l(dst[0:4], ia)
+	store32l(dst[4:8], ib)
+	store32l(dst[8:12], ic)
+	store32l(dst[12:16], id)
+}
diff --git a/twofish/twofish_test.go b/twofish/twofish_test.go
new file mode 100644
index 0000000..303081f
--- /dev/null
+++ b/twofish/twofish_test.go
@@ -0,0 +1,129 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package twofish
+
+import (
+	"bytes"
+	"testing"
+)
+
+var qbox = [2][4][16]byte{
+	{
+		{0x8, 0x1, 0x7, 0xD, 0x6, 0xF, 0x3, 0x2, 0x0, 0xB, 0x5, 0x9, 0xE, 0xC, 0xA, 0x4},
+		{0xE, 0xC, 0xB, 0x8, 0x1, 0x2, 0x3, 0x5, 0xF, 0x4, 0xA, 0x6, 0x7, 0x0, 0x9, 0xD},
+		{0xB, 0xA, 0x5, 0xE, 0x6, 0xD, 0x9, 0x0, 0xC, 0x8, 0xF, 0x3, 0x2, 0x4, 0x7, 0x1},
+		{0xD, 0x7, 0xF, 0x4, 0x1, 0x2, 0x6, 0xE, 0x9, 0xB, 0x3, 0x0, 0x8, 0x5, 0xC, 0xA},
+	},
+	{
+		{0x2, 0x8, 0xB, 0xD, 0xF, 0x7, 0x6, 0xE, 0x3, 0x1, 0x9, 0x4, 0x0, 0xA, 0xC, 0x5},
+		{0x1, 0xE, 0x2, 0xB, 0x4, 0xC, 0x3, 0x7, 0x6, 0xD, 0xA, 0x5, 0xF, 0x9, 0x0, 0x8},
+		{0x4, 0xC, 0x7, 0x5, 0x1, 0x6, 0x9, 0xA, 0x0, 0xE, 0xD, 0x8, 0x2, 0xB, 0x3, 0xF},
+		{0xB, 0x9, 0x5, 0x1, 0xC, 0x3, 0xD, 0xE, 0x6, 0x4, 0x7, 0xF, 0x2, 0x0, 0x8, 0xA},
+	},
+}
+
+// genSbox generates the variable sbox
+func genSbox(qi int, x byte) byte {
+	a0, b0 := x/16, x%16
+	for i := 0; i < 2; i++ {
+		a1 := a0 ^ b0
+		b1 := (a0 ^ ((b0 << 3) | (b0 >> 1)) ^ (a0 << 3)) & 15
+		a0 = qbox[qi][2*i][a1]
+		b0 = qbox[qi][2*i+1][b1]
+	}
+	return (b0 << 4) + a0
+}
+
+func TestSbox(t *testing.T) {
+	for n := range sbox {
+		for m := range sbox[n] {
+			if genSbox(n, byte(m)) != sbox[n][m] {
+				t.Errorf("#%d|%d: sbox value = %d want %d", n, m, sbox[n][m], genSbox(n, byte(m)))
+			}
+		}
+	}
+}
+
+var testVectors = []struct {
+	key []byte
+	dec []byte
+	enc []byte
+}{
+	// These tests are extracted from LibTom
+	{
+		[]byte{0x9F, 0x58, 0x9F, 0x5C, 0xF6, 0x12, 0x2C, 0x32, 0xB6, 0xBF, 0xEC, 0x2F, 0x2A, 0xE8, 0xC3, 0x5A},
+		[]byte{0xD4, 0x91, 0xDB, 0x16, 0xE7, 0xB1, 0xC3, 0x9E, 0x86, 0xCB, 0x08, 0x6B, 0x78, 0x9F, 0x54, 0x19},
+		[]byte{0x01, 0x9F, 0x98, 0x09, 0xDE, 0x17, 0x11, 0x85, 0x8F, 0xAA, 0xC3, 0xA3, 0xBA, 0x20, 0xFB, 0xC3},
+	},
+	{
+		[]byte{0x88, 0xB2, 0xB2, 0x70, 0x6B, 0x10, 0x5E, 0x36, 0xB4, 0x46, 0xBB, 0x6D, 0x73, 0x1A, 0x1E, 0x88,
+			0xEF, 0xA7, 0x1F, 0x78, 0x89, 0x65, 0xBD, 0x44},
+		[]byte{0x39, 0xDA, 0x69, 0xD6, 0xBA, 0x49, 0x97, 0xD5, 0x85, 0xB6, 0xDC, 0x07, 0x3C, 0xA3, 0x41, 0xB2},
+		[]byte{0x18, 0x2B, 0x02, 0xD8, 0x14, 0x97, 0xEA, 0x45, 0xF9, 0xDA, 0xAC, 0xDC, 0x29, 0x19, 0x3A, 0x65},
+	},
+	{
+		[]byte{0xD4, 0x3B, 0xB7, 0x55, 0x6E, 0xA3, 0x2E, 0x46, 0xF2, 0xA2, 0x82, 0xB7, 0xD4, 0x5B, 0x4E, 0x0D,
+			0x57, 0xFF, 0x73, 0x9D, 0x4D, 0xC9, 0x2C, 0x1B, 0xD7, 0xFC, 0x01, 0x70, 0x0C, 0xC8, 0x21, 0x6F},
+		[]byte{0x90, 0xAF, 0xE9, 0x1B, 0xB2, 0x88, 0x54, 0x4F, 0x2C, 0x32, 0xDC, 0x23, 0x9B, 0x26, 0x35, 0xE6},
+		[]byte{0x6C, 0xB4, 0x56, 0x1C, 0x40, 0xBF, 0x0A, 0x97, 0x05, 0x93, 0x1C, 0xB6, 0xD4, 0x08, 0xE7, 0xFA},
+	},
+	// These test are derived from http://www.schneier.com/code/ecb_ival.txt
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x9F, 0x58, 0x9F, 0x5C, 0xF6, 0x12, 0x2C, 0x32, 0xB6, 0xBF, 0xEC, 0x2F, 0x2A, 0xE8, 0xC3, 0x5A},
+	},
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+		},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0xCF, 0xD1, 0xD2, 0xE5, 0xA9, 0xBE, 0x9C, 0xDF, 0x50, 0x1F, 0x13, 0xB8, 0x92, 0xBD, 0x22, 0x48},
+	},
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+		},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x37, 0x52, 0x7B, 0xE0, 0x05, 0x23, 0x34, 0xB8, 0x9F, 0x0C, 0xFC, 0xCA, 0xE8, 0x7C, 0xFA, 0x20},
+	},
+}
+
+func TestCipher(t *testing.T) {
+	for n, tt := range testVectors {
+		// Test if the plaintext (dec) is encrypts to the given
+		// ciphertext (enc) using the given key. Test also if enc can
+		// be decrypted again into dec.
+		c, err := NewCipher(tt.key)
+		if err != nil {
+			t.Errorf("#%d: NewCipher: %v", n, err)
+			return
+		}
+
+		buf := make([]byte, 16)
+		c.Encrypt(buf, tt.dec)
+		if !bytes.Equal(buf, tt.enc) {
+			t.Errorf("#%d: encrypt = %x want %x", n, buf, tt.enc)
+		}
+		c.Decrypt(buf, tt.enc)
+		if !bytes.Equal(buf, tt.dec) {
+			t.Errorf("#%d: decrypt = %x want %x", n, buf, tt.dec)
+		}
+
+		// Test that 16 zero bytes, encrypted 1000 times then decrypted
+		// 1000 times results in zero bytes again.
+		zero := make([]byte, 16)
+		buf = make([]byte, 16)
+		for i := 0; i < 1000; i++ {
+			c.Encrypt(buf, buf)
+		}
+		for i := 0; i < 1000; i++ {
+			c.Decrypt(buf, buf)
+		}
+		if !bytes.Equal(buf, zero) {
+			t.Errorf("#%d: encrypt/decrypt 1000: have %x want %x", n, buf, zero)
+		}
+	}
+}
diff --git a/xtea/block.go b/xtea/block.go
new file mode 100644
index 0000000..bf5d245
--- /dev/null
+++ b/xtea/block.go
@@ -0,0 +1,66 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+	Implementation adapted from Needham and Wheeler's paper:
+	http://www.cix.co.uk/~klockstone/xtea.pdf
+
+	A precalculated look up table is used during encryption/decryption for values that are based purely on the key.
+*/
+
+package xtea
+
+// XTEA is based on 64 rounds.
+const numRounds = 64
+
+// blockToUint32 reads an 8 byte slice into two uint32s.
+// The block is treated as big endian.
+func blockToUint32(src []byte) (uint32, uint32) {
+	r0 := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
+	r1 := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
+	return r0, r1
+}
+
+// uint32ToBlock writes two uint32s into an 8 byte data block.
+// Values are written as big endian.
+func uint32ToBlock(v0, v1 uint32, dst []byte) {
+	dst[0] = byte(v0 >> 24)
+	dst[1] = byte(v0 >> 16)
+	dst[2] = byte(v0 >> 8)
+	dst[3] = byte(v0)
+	dst[4] = byte(v1 >> 24)
+	dst[5] = byte(v1 >> 16)
+	dst[6] = byte(v1 >> 8)
+	dst[7] = byte(v1 >> 0)
+}
+
+// encryptBlock encrypts a single 8 byte block using XTEA.
+func encryptBlock(c *Cipher, dst, src []byte) {
+	v0, v1 := blockToUint32(src)
+
+	// Two rounds of XTEA applied per loop
+	for i := 0; i < numRounds; {
+		v0 += ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
+		i++
+		v1 += ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
+		i++
+	}
+
+	uint32ToBlock(v0, v1, dst)
+}
+
+// decryptBlock decrypt a single 8 byte block using XTEA.
+func decryptBlock(c *Cipher, dst, src []byte) {
+	v0, v1 := blockToUint32(src)
+
+	// Two rounds of XTEA applied per loop
+	for i := numRounds; i > 0; {
+		i--
+		v1 -= ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
+		i--
+		v0 -= ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
+	}
+
+	uint32ToBlock(v0, v1, dst)
+}
diff --git a/xtea/cipher.go b/xtea/cipher.go
new file mode 100644
index 0000000..3ed0581
--- /dev/null
+++ b/xtea/cipher.go
@@ -0,0 +1,89 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package xtea implements XTEA encryption, as defined in Needham and Wheeler's
+// 1997 technical report, "Tea extensions."
+package xtea
+
+// For details, see http://www.cix.co.uk/~klockstone/xtea.pdf
+
+import "strconv"
+
+// The XTEA block size in bytes.
+const BlockSize = 8
+
+// A Cipher is an instance of an XTEA cipher using a particular key.
+// table contains a series of precalculated values that are used each round.
+type Cipher struct {
+	table [64]uint32
+}
+
+type KeySizeError int
+
+func (k KeySizeError) Error() string {
+	return "crypto/xtea: invalid key size " + strconv.Itoa(int(k))
+}
+
+// NewCipher creates and returns a new Cipher.
+// The key argument should be the XTEA key.
+// XTEA only supports 128 bit (16 byte) keys.
+func NewCipher(key []byte) (*Cipher, error) {
+	k := len(key)
+	switch k {
+	default:
+		return nil, KeySizeError(k)
+	case 16:
+		break
+	}
+
+	c := new(Cipher)
+	initCipher(c, key)
+
+	return c, nil
+}
+
+// BlockSize returns the XTEA block size, 8 bytes.
+// It is necessary to satisfy the Block interface in the
+// package "crypto/cipher".
+func (c *Cipher) BlockSize() int { return BlockSize }
+
+// Encrypt encrypts the 8 byte buffer src using the key and stores the result in dst.
+// Note that for amounts of data larger than a block,
+// it is not safe to just call Encrypt on successive blocks;
+// instead, use an encryption mode like CBC (see crypto/cipher/cbc.go).
+func (c *Cipher) Encrypt(dst, src []byte) { encryptBlock(c, dst, src) }
+
+// Decrypt decrypts the 8 byte buffer src using the key k and stores the result in dst.
+func (c *Cipher) Decrypt(dst, src []byte) { decryptBlock(c, dst, src) }
+
+// Reset zeros the table, so that it will no longer appear in the process's memory.
+func (c *Cipher) Reset() {
+	for i := 0; i < len(c.table); i++ {
+		c.table[i] = 0
+	}
+}
+
+// initCipher initializes the cipher context by creating a look up table
+// of precalculated values that are based on the key.
+func initCipher(c *Cipher, key []byte) {
+	// Load the key into four uint32s
+	var k [4]uint32
+	for i := 0; i < len(k); i++ {
+		j := i << 2 // Multiply by 4
+		k[i] = uint32(key[j+0])<<24 | uint32(key[j+1])<<16 | uint32(key[j+2])<<8 | uint32(key[j+3])
+	}
+
+	// Precalculate the table
+	const delta = 0x9E3779B9
+	var sum uint32 = 0
+
+	// Two rounds of XTEA applied per loop
+	for i := 0; i < numRounds; {
+		c.table[i] = sum + k[sum&3]
+		i++
+		sum += delta
+		c.table[i] = sum + k[(sum>>11)&3]
+		i++
+	}
+}
diff --git a/xtea/xtea_test.go b/xtea/xtea_test.go
new file mode 100644
index 0000000..217d96a
--- /dev/null
+++ b/xtea/xtea_test.go
@@ -0,0 +1,246 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xtea
+
+import (
+	"testing"
+)
+
+// A sample test key for when we just want to initialize a cipher
+var testKey = []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
+
+// Test that the block size for XTEA is correct
+func TestBlocksize(t *testing.T) {
+	if BlockSize != 8 {
+		t.Errorf("BlockSize constant - expected 8, got %d", BlockSize)
+		return
+	}
+
+	c, err := NewCipher(testKey)
+	if err != nil {
+		t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+		return
+	}
+
+	result := c.BlockSize()
+	if result != 8 {
+		t.Errorf("BlockSize function - expected 8, got %d", result)
+		return
+	}
+}
+
+// A series of test values to confirm that the Cipher.table array was initialized correctly
+var testTable = []uint32{
+	0x00112233, 0x6B1568B8, 0xE28CE030, 0xC5089E2D, 0xC5089E2D, 0x1EFBD3A2, 0xA7845C2A, 0x78EF0917,
+	0x78EF0917, 0x172682D0, 0x5B6AC714, 0x822AC955, 0x3DE68511, 0xDC1DFECA, 0x2062430E, 0x3611343F,
+	0xF1CCEFFB, 0x900469B4, 0xD448ADF8, 0x2E3BE36D, 0xB6C46BF5, 0x994029F2, 0x994029F2, 0xF3335F67,
+	0x6AAAD6DF, 0x4D2694DC, 0x4D2694DC, 0xEB5E0E95, 0x2FA252D9, 0x4551440A, 0x121E10D6, 0xB0558A8F,
+	0xE388BDC3, 0x0A48C004, 0xC6047BC0, 0x643BF579, 0xA88039BD, 0x02736F32, 0x8AFBF7BA, 0x5C66A4A7,
+	0x5C66A4A7, 0xC76AEB2C, 0x3EE262A4, 0x215E20A1, 0x215E20A1, 0x7B515616, 0x03D9DE9E, 0x1988CFCF,
+	0xD5448B8B, 0x737C0544, 0xB7C04988, 0xDE804BC9, 0x9A3C0785, 0x3873813E, 0x7CB7C582, 0xD6AAFAF7,
+	0x4E22726F, 0x309E306C, 0x309E306C, 0x8A9165E1, 0x1319EE69, 0xF595AC66, 0xF595AC66, 0x4F88E1DB,
+}
+
+// Test that the cipher context is initialized correctly
+func TestCipherInit(t *testing.T) {
+	c, err := NewCipher(testKey)
+	if err != nil {
+		t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+		return
+	}
+
+	for i := 0; i < len(c.table); i++ {
+		if c.table[i] != testTable[i] {
+			t.Errorf("NewCipher() failed to initialize Cipher.table[%d] correctly. Expected %08X, got %08X", i, testTable[i], c.table[i])
+			break
+		}
+	}
+}
+
+// Test that invalid key sizes return an error
+func TestInvalidKeySize(t *testing.T) {
+	// Test a long key
+	key := []byte{
+		0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+		0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
+	}
+
+	_, err := NewCipher(key)
+	if err == nil {
+		t.Errorf("Invalid key size %d didn't result in an error.", len(key))
+	}
+
+	// Test a short key
+	key = []byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}
+
+	_, err = NewCipher(key)
+	if err == nil {
+		t.Errorf("Invalid key size %d didn't result in an error.", len(key))
+	}
+}
+
+// Test that we can correctly decode some bytes we have encoded
+func TestEncodeDecode(t *testing.T) {
+	original := []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}
+	input := original
+	output := make([]byte, BlockSize)
+
+	c, err := NewCipher(testKey)
+	if err != nil {
+		t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+		return
+	}
+
+	// Encrypt the input block
+	c.Encrypt(output, input)
+
+	// Check that the output does not match the input
+	differs := false
+	for i := 0; i < len(input); i++ {
+		if output[i] != input[i] {
+			differs = true
+			break
+		}
+	}
+	if differs == false {
+		t.Error("Cipher.Encrypt: Failed to encrypt the input block.")
+		return
+	}
+
+	// Decrypt the block we just encrypted
+	input = output
+	output = make([]byte, BlockSize)
+	c.Decrypt(output, input)
+
+	// Check that the output from decrypt matches our initial input
+	for i := 0; i < len(input); i++ {
+		if output[i] != original[i] {
+			t.Errorf("Decrypted byte %d differed. Expected %02X, got %02X\n", i, original[i], output[i])
+			return
+		}
+	}
+}
+
+// Test Vectors
+type CryptTest struct {
+	key        []byte
+	plainText  []byte
+	cipherText []byte
+}
+
+var CryptTests = []CryptTest{
+	// These were sourced from http://www.freemedialibrary.com/index.php/XTEA_test_vectors
+	{
+		[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+		[]byte{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48},
+		[]byte{0x49, 0x7d, 0xf3, 0xd0, 0x72, 0x61, 0x2c, 0xb5},
+	},
+	{
+		[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+		[]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+		[]byte{0xe7, 0x8f, 0x2d, 0x13, 0x74, 0x43, 0x41, 0xd8},
+	},
+	{
+		[]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f},
+		[]byte{0x5a, 0x5b, 0x6e, 0x27, 0x89, 0x48, 0xd7, 0x7f},
+		[]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+	},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48},
+		[]byte{0xa0, 0x39, 0x05, 0x89, 0xf8, 0xb8, 0xef, 0xa5},
+	},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+		[]byte{0xed, 0x23, 0x37, 0x5a, 0x82, 0x1a, 0x8c, 0x2d},
+	},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x70, 0xe1, 0x22, 0x5d, 0x6e, 0x4e, 0x76, 0x55},
+		[]byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41},
+	},
+
+	// These vectors are from http://wiki.secondlife.com/wiki/XTEA_Strong_Encryption_Implementation#Bouncy_Castle_C.23_API
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0xDE, 0xE9, 0xD4, 0xD8, 0xF7, 0x13, 0x1E, 0xD9},
+	},
+	{
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+		[]byte{0x06, 0x5C, 0x1B, 0x89, 0x75, 0xC6, 0xA8, 0x16},
+	},
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89, 0x34, 0x56, 0x78, 0x9A},
+		[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+		[]byte{0x1F, 0xF9, 0xA0, 0x26, 0x1A, 0xC6, 0x42, 0x64},
+	},
+	{
+		[]byte{0x01, 0x23, 0x45, 0x67, 0x12, 0x34, 0x56, 0x78, 0x23, 0x45, 0x67, 0x89, 0x34, 0x56, 0x78, 0x9A},
+		[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
+		[]byte{0x8C, 0x67, 0x15, 0x5B, 0x2E, 0xF9, 0x1E, 0xAD},
+	},
+}
+
+// Test encryption
+func TestCipherEncrypt(t *testing.T) {
+	for i, tt := range CryptTests {
+		c, err := NewCipher(tt.key)
+		if err != nil {
+			t.Errorf("NewCipher(%d bytes), vector %d = %s", len(tt.key), i, err)
+			continue
+		}
+
+		out := make([]byte, len(tt.plainText))
+		c.Encrypt(out, tt.plainText)
+
+		for j := 0; j < len(out); j++ {
+			if out[j] != tt.cipherText[j] {
+				t.Errorf("Cipher.Encrypt %d: out[%d] = %02X, expected %02X", i, j, out[j], tt.cipherText[j])
+				break
+			}
+		}
+	}
+}
+
+// Test decryption
+func TestCipherDecrypt(t *testing.T) {
+	for i, tt := range CryptTests {
+		c, err := NewCipher(tt.key)
+		if err != nil {
+			t.Errorf("NewCipher(%d bytes), vector %d = %s", len(tt.key), i, err)
+			continue
+		}
+
+		out := make([]byte, len(tt.cipherText))
+		c.Decrypt(out, tt.cipherText)
+
+		for j := 0; j < len(out); j++ {
+			if out[j] != tt.plainText[j] {
+				t.Errorf("Cipher.Decrypt %d: out[%d] = %02X, expected %02X", i, j, out[j], tt.plainText[j])
+				break
+			}
+		}
+	}
+}
+
+// Test resetting the cipher context
+func TestReset(t *testing.T) {
+	c, err := NewCipher(testKey)
+	if err != nil {
+		t.Errorf("NewCipher(%d bytes) = %s", len(testKey), err)
+		return
+	}
+
+	c.Reset()
+	for i := 0; i < len(c.table); i++ {
+		if c.table[i] != 0 {
+			t.Errorf("Cipher.Reset: Failed to clear Cipher.table[%d]. expected 0, got %08X", i, c.table[i])
+			return
+		}
+	}
+}