Browse Source

Use `crypto/aes` on AMD64 when it's safe to do so. #7

Where "safe" means AES-NI and PCLMULQDQ are both supported by the CPU
and the Go runtime library has code to actually use both.
Yawning Angel 2 years ago
parent
commit
246afd4e80
5 changed files with 123 additions and 2 deletions
  1. 9 1
      aes.go
  2. 25 1
      aes_test.go
  3. 49 0
      aesni.go
  4. 29 0
      aesni_stub.go
  5. 11 0
      cpuid_amd64.s

+ 9 - 1
aes.go

@@ -24,6 +24,7 @@
 package bsaes
 
 import (
+	"crypto/aes"
 	"crypto/cipher"
 	"errors"
 	"math"
@@ -33,7 +34,10 @@ import (
 	"git.schwanenlied.me/yawning/bsaes.git/ct64"
 )
 
-var ctor = ct64.NewCipher
+var (
+	useCryptoAES = false
+	ctor         = ct64.NewCipher
+)
 
 type resetAble interface {
 	Reset()
@@ -48,6 +52,9 @@ func NewCipher(key []byte) (cipher.Block, error) {
 	default:
 		return nil, errors.New("aes: Invalid key size")
 	}
+	if isCryptoAESSafe() {
+		return aes.NewCipher(key)
+	}
 
 	blk := ctor(key)
 	r := blk.(resetAble)
@@ -68,4 +75,5 @@ func init() {
 	default:
 		panic("bsaes: unsupported architecture")
 	}
+	useCryptoAES = isCryptoAESSafe()
 }

+ 25 - 1
aes_test.go

@@ -29,6 +29,13 @@ type Impl struct {
 var (
 	implCt32 = &Impl{"ct32", ct32.NewCipher}
 	implCt64 = &Impl{"ct64", ct64.NewCipher}
+	implRuntime = &Impl{"runtime", func(k []byte) cipher.Block {
+		blk, err := NewCipher(k)
+		if err != nil {
+			panic("implRuntime: NewCipher failed: " + err.Error())
+		}
+		return blk
+	}}
 
 	impls      = []*Impl{implCt32, implCt64}
 	nativeImpl = implCt64
@@ -210,9 +217,16 @@ func TestCTR_keystream(t *testing.T) {
 			strideSz = 2 * 16
 		case "ct64":
 			strideSz = 4 * 16
+		case "runtime":
+			// The CTR tests are tailored towards the bsaes CTR 
+			// so there is not much sense in testing `crypto/aes`'s,
+			// when it's using AES-NI and assembly.
+			t.Logf("Skipping CTR tests: %v\n", impl.name)
+			continue
 		default:
 			panic("unable to determine stride")
 		}
+		t.Logf("Testing implementation: %v\n", impl.name)
 
 		key := make([]byte, 16)
 		if _, err := rand.Read(key[:]); err != nil {
@@ -635,7 +649,7 @@ func doBenchGCM(b *testing.B, impl *Impl, ksz, n int) {
 }
 
 func implIsNative(impl *Impl) bool {
-	return impl == nativeImpl
+	return impl == nativeImpl || impl == implRuntime
 }
 
 func doBench(b *testing.B, impl *Impl) {
@@ -673,6 +687,13 @@ func Benchmark_ct64(b *testing.B) {
 	doBench(b, implCt64)
 }
 
+func Benchmark_runtime(b *testing.B) {
+	if !useCryptoAES {
+		b.SkipNow()
+	}
+	doBench(b, implRuntime)
+}
+
 func init() {
 	maxUintptr := uint64(^uintptr(0))
 	switch maxUintptr {
@@ -683,4 +704,7 @@ func init() {
 	default:
 		panic("bsaes: unsupported architecture")
 	}
+	if useCryptoAES {
+		impls = append(impls, implRuntime)
+	}
 }

+ 49 - 0
aesni.go

@@ -0,0 +1,49 @@
+// Copyright (c) 2017 Yawning Angel <yawning at schwanenlied dot me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// +build go1.6
+// +build !gccgo
+// +build amd64
+
+package bsaes
+
+//go:noescape
+func cpuidAMD64(cpuidParams *uint32)
+
+func isCryptoAESSafe() bool {
+	return supportsAESNI()
+}
+
+func supportsAESNI() bool {
+	const (
+		pclmulBit = 1 << 1
+		aesniBit  = 1 << 25
+	)
+
+	// Check for AES-NI support.
+	// CPUID.(EAX=01H, ECX=0H):ECX.AESNI[bit 25]==1
+	//                         ECX.PCLMUL[bit 1]==1
+	regs := [4]uint32{0x01}
+	cpuidAMD64(&regs[0])
+
+	return regs[2]&pclmulBit != 0 && regs[2]&aesniBit != 0
+}

+ 29 - 0
aesni_stub.go

@@ -0,0 +1,29 @@
+// Copyright (c) 2017 Yawning Angel <yawning at schwanenlied dot me>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// +build !go1.6 gccgo !amd64
+
+package bsaes
+
+func isCryptoAESSafe() bool {
+	return false
+}

+ 11 - 0
cpuid_amd64.s

@@ -0,0 +1,11 @@
+// func cpuidAMD64(cpuidParams *uint32)
+TEXT ·cpuidAMD64(SB),4,$0-8
+	MOVQ cpuidParams+0(FP), R15
+	MOVL 0(R15), AX
+	MOVL 8(R15), CX
+	CPUID
+	MOVL AX, 0(R15)
+	MOVL BX, 4(R15)
+	MOVL CX, 8(R15)
+	MOVL DX, 12(R15)
+	RET