diff --git a/src/crypto/aes/aes_test.go b/src/crypto/aes/aes_test.go
index cfe75f4..1d6b666 100644
--- a/src/crypto/aes/aes_test.go
+++ b/src/crypto/aes/aes_test.go
@@ -68,7 +68,7 @@
c.Encrypt(out, tt.in)
for j, v := range out {
if v != tt.out[j] {
- t.Errorf("Cipher.Encrypt %d: out[%d] = %#x, want %#x", i, j, v, tt.out[j])
+ t.Errorf("Cipher.Encrypt(%d) %d: out[%d] = %#x, want %#x", len(tt.key)*8, i, j, v, tt.out[j])
break
}
}
@@ -91,7 +91,7 @@
c.Decrypt(plain, tt.out)
for j, v := range plain {
if v != tt.in[j] {
- t.Errorf("decryptBlock %d: plain[%d] = %#x, want %#x", i, j, v, tt.in[j])
+ t.Errorf("Cipher.Decrypt(%d) %d: plain[%d] = %#x, want %#x", len(tt.key)*8, i, j, v, tt.in[j])
break
}
}
diff --git a/src/crypto/internal/fips140/aes/aes_noasm.go b/src/crypto/internal/fips140/aes/aes_noasm.go
index 8ba5402..a561288 100644
--- a/src/crypto/internal/fips140/aes/aes_noasm.go
+++ b/src/crypto/internal/fips140/aes/aes_noasm.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (!amd64 && !s390x && !ppc64 && !ppc64le && !arm64) || purego
+//go:build (!amd64 && !s390x && !ppc64 && !ppc64le && !arm64 && !riscv64) || purego
package aes
diff --git a/src/crypto/internal/fips140/aes/aes_riscv64.go b/src/crypto/internal/fips140/aes/aes_riscv64.go
new file mode 100644
index 0000000..8b780b6
--- /dev/null
+++ b/src/crypto/internal/fips140/aes/aes_riscv64.go
@@ -0,0 +1,110 @@
+// Copyright 2026 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.
+
+//go:build !purego
+
+package aes
+
+import (
+ "crypto/internal/fips140deps/cpu"
+ "crypto/internal/impl"
+)
+
+func rotwLE(t uint32) uint32 {
+ return (t >> 8) | (t << 24)
+}
+
+func subwLE(t uint32) uint32 {
+ return uint32(sbox0[t&0xff]) |
+ uint32(sbox0[(t>>8)&0xff])<<8 |
+ uint32(sbox0[(t>>16)&0xff])<<16 |
+ uint32(sbox0[t>>24])<<24
+}
+
+var supportsAES = cpu.RISCV64HasZvkned
+
+func init() {
+ if cpu.RISCV64 {
+ impl.Register("aes", "ZVKNED", &supportsAES)
+ }
+}
+
+type block struct {
+ blockExpanded
+}
+
+func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32)
+func encryptBlockAsm(nr int, xk *uint32, dst, src *byte)
+func decryptBlockAsm(nr int, xk *uint32, dst, src *byte)
+
+func checkGenericIsExpected() {}
+
+// expandKeyAES192LE all riscv AES encrypt and decrypt using
+// little endian key, derived from expandKeyGeneric
+func expandKeyAES192LE(c *blockExpanded, key []byte) {
+ const nk = 6
+ const n = 52
+
+ for i := 0; i < nk; i++ {
+ c.enc[i] = uint32(key[4*i]) |
+ uint32(key[4*i+1])<<8 |
+ uint32(key[4*i+2])<<16 |
+ uint32(key[4*i+3])<<24
+ }
+ for i := nk; i < n; i++ {
+ t := c.enc[i-1]
+ if i%nk == 0 {
+ t = subwLE(rotwLE(t)) ^ uint32(powx[i/nk-1])
+ }
+ c.enc[i] = c.enc[i-nk] ^ t
+ }
+ for i := 0; i < n; i += 4 {
+ ei := n - i - 4
+ for j := 0; j < 4; j++ {
+ c.dec[i+j] = c.enc[ei+j]
+ }
+ }
+}
+
+func newBlock(c *Block, key []byte) *Block {
+ switch len(key) {
+ case aes128KeySize:
+ c.rounds = aes128Rounds
+ case aes192KeySize:
+ c.rounds = aes192Rounds
+ case aes256KeySize:
+ c.rounds = aes256Rounds
+ }
+
+ if supportsAES {
+ if c.rounds == aes192Rounds {
+ expandKeyAES192LE(&c.blockExpanded, key)
+ return c
+ }
+ expandKeyAsm(c.rounds, &key[0], &c.enc[0], &c.dec[0])
+ } else {
+ expandKeyGeneric(&c.blockExpanded, key)
+ }
+ return c
+}
+
+func EncryptionKeySchedule(c *Block) []uint32 {
+ return c.enc[:c.roundKeysSize()]
+}
+
+func encryptBlock(c *Block, dst, src []byte) {
+ if supportsAES {
+ encryptBlockAsm(c.rounds, &c.enc[0], &dst[0], &src[0])
+ } else {
+ encryptBlockGeneric(&c.blockExpanded, dst, src)
+ }
+}
+
+func decryptBlock(c *Block, dst, src []byte) {
+ if supportsAES {
+ decryptBlockAsm(c.rounds, &c.dec[0], &dst[0], &src[0])
+ } else {
+ decryptBlockGeneric(&c.blockExpanded, dst, src)
+ }
+}
diff --git a/src/crypto/internal/fips140/aes/aes_riscv64.s b/src/crypto/internal/fips140/aes/aes_riscv64.s
new file mode 100644
index 0000000..6dbdf8af
--- /dev/null
+++ b/src/crypto/internal/fips140/aes/aes_riscv64.s
@@ -0,0 +1,236 @@
+// Copyright 2026 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.
+
+//go:build !purego
+
+#include "textflag.h"
+
+// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte)
+TEXT ·encryptBlockAsm(SB),NOSPLIT,$0
+ MOV nr+0(FP), X10
+ MOV xk+8(FP), X11
+ MOV dst+16(FP), X12
+ MOV src+24(FP), X13
+
+ VSETIVLI $4, E32, M1, TA, MA, ZERO
+
+ VLE32V (X13), V2 // V2 = src
+
+ // Load all encrypt round keys
+ // Round 0
+ VLE32V (X11), V10
+ VAESZVS V10, V2
+ ADD $16, X11
+
+ // Round middles 1 .. nr - 2
+ VLE32V (X11), V11
+ VAESEMVS V11, V2
+ ADD $16, X11
+ VLE32V (X11), V12
+ VAESEMVS V12, V2
+ ADD $16, X11
+ VLE32V (X11), V13
+ VAESEMVS V13, V2
+ ADD $16, X11
+ VLE32V (X11), V14
+ VAESEMVS V14, V2
+ ADD $16, X11
+ VLE32V (X11), V15
+ VAESEMVS V15, V2
+ ADD $16, X11
+ VLE32V (X11), V16
+ VAESEMVS V16, V2
+ ADD $16, X11
+ VLE32V (X11), V17
+ VAESEMVS V17, V2
+ ADD $16, X11
+ VLE32V (X11), V18
+ VAESEMVS V18, V2
+ ADD $16, X11
+ VLE32V (X11), V19
+ VAESEMVS V19, V2
+ ADD $16, X11
+
+ SUB $10, X10, X14
+ BEQZ X14, encLastRK
+ SUB $12, X10, X14
+ BEQZ X14, encRK12
+
+ // round key 14
+ VLE32V (X11), V11
+ VAESEMVS V11, V2
+ ADD $16, X11
+
+ VLE32V (X11), V12
+ VAESEMVS V12, V2
+ ADD $16, X11
+encRK12:
+ VLE32V (X11), V13
+ VAESEMVS V13, V2
+ ADD $16, X11
+
+ VLE32V (X11), V14
+ VAESEMVS V14, V2
+ ADD $16, X11
+
+encLastRK:
+ VLE32V (X11), V10
+ VAESEFVS V10, V2
+ VSE32V V2, (X12)
+ RET
+
+// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte)
+TEXT ·decryptBlockAsm(SB),NOSPLIT,$0
+ MOV nr+0(FP), X10
+ MOV xk+8(FP), X11
+ MOV dst+16(FP), X12
+ MOV src+24(FP), X13
+
+ VSETIVLI $4, E32, M1, TA, MA, ZERO
+
+ VLE32V (X13), V2 // V2 = src
+
+ // Load all decrypt round keys
+ // Round 0
+ VLE32V (X11), V10
+ VAESZVS V10, V2
+ ADD $16, X11
+
+ // Round middles 1 .. nr - 2
+ VLE32V (X11), V11
+ VAESDMVS V11, V2
+ ADD $16, X11
+ VLE32V (X11), V12
+ VAESDMVS V12, V2
+ ADD $16, X11
+ VLE32V (X11), V13
+ VAESDMVS V13, V2
+ ADD $16, X11
+ VLE32V (X11), V14
+ VAESDMVS V14, V2
+ ADD $16, X11
+ VLE32V (X11), V15
+ VAESDMVS V15, V2
+ ADD $16, X11
+ VLE32V (X11), V16
+ VAESDMVS V16, V2
+ ADD $16, X11
+ VLE32V (X11), V17
+ VAESDMVS V17, V2
+ ADD $16, X11
+ VLE32V (X11), V18
+ VAESDMVS V18, V2
+ ADD $16, X11
+ VLE32V (X11), V19
+ VAESDMVS V19, V2
+ ADD $16, X11
+
+ SUB $10, X10, X14
+ BEQZ X14, decLastRK
+ SUB $12, X10, X14
+ BEQZ X14, decRK12
+
+ // round key 14
+ VLE32V (X11), V11
+ VAESDMVS V11, V2
+ ADD $16, X11
+
+ VLE32V (X11), V12
+ VAESDMVS V12, V2
+ ADD $16, X11
+decRK12:
+ VLE32V (X11), V13
+ VAESDMVS V13, V2
+ ADD $16, X11
+ VLE32V (X11), V14
+ VAESDMVS V14, V2
+ ADD $16, X11
+
+decLastRK:
+ VLE32V (X11), V10
+ VAESDFVS V10, V2
+ VSE32V V2, (X12)
+ RET
+
+#define kf1m(ROUND, CUR, NEXT) \
+ VAESKF1VI ROUND, CUR, NEXT; \
+ VSE32V NEXT, (X12); \
+ ADD $16, X12; \
+ VSE32V NEXT, (X13); \
+ SUB $16, X13; \
+
+#define kf2m(ROUND, CUR, NEXT) \
+ VAESKF2VI ROUND, CUR, NEXT; \
+ VSE32V NEXT, (X12); \
+ ADD $16, X12; \
+ VSE32V NEXT, (X13); \
+ SUB $16, X13; \
+
+// func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32)
+TEXT ·expandKeyAsm(SB),NOSPLIT,$0
+ MOV nr+0(FP), X10
+ MOV key+8(FP), X11
+ MOV enc+16(FP), X12
+ MOV dec+24(FP), X13
+
+ VSETIVLI $4, E32, M1, TA, MA, ZERO
+
+ SUB $14, X10, X14
+ BEQZ X14, expand256
+
+expand128:
+ ADD $160, X13
+ VLE32V (X11), V2
+ VSE32V V2, (X12)
+ ADD $16, X12
+ VSE32V V2, (X13)
+ SUB $16, X13
+
+ kf1m($1, V2, V3)
+ kf1m($2, V3, V4)
+ kf1m($3, V4, V5)
+ kf1m($4, V5, V6)
+ kf1m($5, V6, V7)
+ kf1m($6, V7, V8)
+ kf1m($7, V8, V9)
+ kf1m($8, V9, V10)
+ kf1m($9, V10, V11)
+
+ VAESKF1VI $10, V11, V12
+ VSE32V V12, (X12)
+ VSE32V V12, (X13)
+ RET
+
+expand256:
+ ADD $224, X13
+ VLE32V (X11), V2
+ ADD $16, X11
+ VLE32V (X11), V3
+ VSE32V V2, (X12)
+ ADD $16, X12
+ VSE32V V2, (X13)
+ SUB $16, X13
+ VSE32V V3, (X12)
+ ADD $16, X12
+ VSE32V V3, (X13)
+ SUB $16, X13
+
+
+ kf2m($2, V3, V2)
+ kf2m($3, V2, V3)
+ kf2m($4, V3, V2)
+ kf2m($5, V2, V3)
+ kf2m($6, V3, V2)
+ kf2m($7, V2, V3)
+ kf2m($8, V3, V2)
+ kf2m($9, V2, V3)
+ kf2m($10, V3, V2)
+ kf2m($11, V2, V3)
+ kf2m($12, V3, V2)
+ kf2m($13, V2, V3)
+
+ VAESKF2VI $14, V3, V2
+ VSE32V V2, (X12)
+ VSE32V V2, (X13)
+ RET
diff --git a/src/crypto/internal/fips140deps/cpu/cpu.go b/src/crypto/internal/fips140deps/cpu/cpu.go
index 269e213..1580239 100644
--- a/src/crypto/internal/fips140deps/cpu/cpu.go
+++ b/src/crypto/internal/fips140deps/cpu/cpu.go
@@ -15,6 +15,7 @@
ARM64 = goarch.IsArm64 == 1
PPC64 = goarch.IsPpc64 == 1
PPC64le = goarch.IsPpc64le == 1
+ RISCV64 = goarch.IsRiscv64 == 1
)
var (
@@ -29,6 +30,7 @@
RISCV64HasV = cpu.RISCV64.HasV
RISCV64HasZvbb = cpu.RISCV64.HasZvbb
+ RISCV64HasZvkned = cpu.RISCV64.HasZvkned
RISCV64HasZvknha = cpu.RISCV64.HasZvknha
RISCV64HasZvknhb = cpu.RISCV64.HasZvknhb