Base64规定:
4 个 Base64 字符表示 3 个二进制字节,因为:64 * 64 * 64 * 64 = 256 * 256 * 256。
Base64 将三个字节转化成四个字节,因此 Base64 编码后的文本,会比原文本大出三分之一左右。
Base64共有65个字符,他们分别是: 大写字母 A-Z、小写字母 a-z、数字 0-9、符号 "+"、"/"(再加上作为垫字的 "=",垫字是当生成的 Base64 字符串的个数不是 4 的倍数时,添加在尾部的字符)。
Base64将其他所有符号都转换成这65个字符集中的字符。
Base64字符判断思路
根据上面的规则,要判断一个字符串是否base64编码 就需要使用正则和其自带的编码和解码来判断,另外一个特例 就是单字符串为纯数字时 某些情况下利用自带的编解码都是可以通过的,这里将纯数字的情况全部视为非Base64字符
是否Base64字符判断golang实现代码
注意,下面的代码需要引入go依赖 go-str-utils,
安装方法 go get -u github.com/tekintian/go-str-utils
// 判断字符串是否是base64字符 可准确判断是或者否 纯数字被视为非base64字符
//
// Base64规定: 4 个 Base64 字符表示 3 个二进制字节,因为:64 * 64 * 64 * 64 = 256 * 256 * 256。
// Base64 将三个字节转化成四个字节。
// Base64共有65个字符,他们分别是: 大写字母 A-Z、小写字母 a-z、数字 0-9、符号 "+"、"/"(再加上作为垫字的 "=",
// 垫字是当生成的 Base64 字符串的个数不是 4 的倍数时,添加在尾部的字符)。
// Base64将其他所有符号都转换成这65个字符集中的字符。
// @author: TekinTian <tekintian@gmail.com>
// @see https://dev.tekin.cn/
func JudgeBase64(str string) bool {
// 先断言字符串的长度是否符合base64规范, 即必须是4的倍数; 把这个放在开始,因为这个判断的时间复杂度最低
if len(str) < 4 || len(str)%4 != 0 {
return false
}
// 在用正则来判断 字符串的规则是否符合
// 纯数字 这个是一个特例,他不是base64但是 某些情况下可以通过base64编码和解码 这里将他们全部视为非base64字符
re, _ := GetRegexp(`^\d+$`)
if re.MatchString(str) {
return false
}
// 正则后向非断言 (?!\d+$) 断言后面非连续的纯数字
// 判断字符串是否符合base64的编码格式 即是由64个字符和垫字符组成
re, _ = GetRegexp(`^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$`)
matched := re.MatchString(str)
if !matched {
return false
}
// 在使用自身带的解码和转码 在查看他么的值是否相等,如果相等说明是base64 否则不是
deStr, err := base64.StdEncoding.DecodeString(str)
if err != nil { // 无法解码,肯定不是base64
return false
}
bs64Str := base64.StdEncoding.EncodeToString(deStr)
//如果解码后再转码和原来的字符一样说明是base64 否则不是
return str == bs64Str
}
JudgeBase64单元测试用例
// base64字符判断单元测试用例
func TestJudgeBase64(t *testing.T) {
cases := []struct {
str string
expected bool
}{
{str: "00000000", expected: false},
{str: "JTIyMTIzJTIy", expected: true},
{str: "JTIyMSUyMg==", expected: true},
{str: "123456 dfadsfdagf!", expected: false},
{str: "1", expected: false},
{str: "123", expected: false},
{str: "1234", expected: false},
{str: "0000", expected: false},
}
for _, v := range cases {
t.Run(v.str, func(t *testing.T) {
// 业务逻辑测试
ret := strutils.JudgeBase64(v.str)
if ret != v.expected {
t.Fatalf(" %v test failed, expected %v, got %v", v.str, v.expected, ret)
}
})
}
}
JudgeBase64模糊测试用例
// 模糊测试命令: go test -fuzz=FuzzJudgeBase64 -fuzztime 30s
func FuzzJudgeBase64(f *testing.F) {
// 测试种子语料
testcases := []string{
base64.StdEncoding.EncodeToString([]byte("hello world")),
base64.StdEncoding.EncodeToString([]byte("你好中国")),
base64.StdEncoding.EncodeToString([]byte("%$#@@*(*%$%")),
base64.StdEncoding.EncodeToString([]byte("123")),
}
for _, tc := range testcases {
f.Add(tc) // 提供种子语料库
}
f.Fuzz(func(t *testing.T, orig string) {
// 首先需要对源语料 进行过滤, 因为这些情况 JudgeBase64 一定会返回false;
judgeRes := strutils.JudgeBase64(orig)
orig = strings.TrimSpace(orig)
// 长度非4的倍数
if (len(orig) < 4 || len(orig)%4 != 0) && !judgeRes {
t.Skipf("测试数据 %v 长度不符合base64规则,跳过", orig)
}
re, _ := strutils.GetRegexp(`^\d+$`)
//纯数字
if re.MatchString(orig) && !judgeRes {
t.Skipf("测试数据 %v 是纯数字,跳过", orig)
}
//包含非base64允许字符
re, _ = strutils.GetRegexp(`^([0-9a-zA-Z+/=]+)$`)
if !re.MatchString(orig) && !judgeRes {
t.Skipf("测试数据 %v 包含非base64允许字符,跳过", orig)
}
// 其他情况
if !judgeRes { // 判断结果为非base64
// 再次进行验证, 将源进行base64解码,然后在对结果进行编码, 如果没有异常,且他们相等 那么说明JudgeBase64 判断失误! 否则判断成功
b, err := base64.StdEncoding.DecodeString(orig)
src := base64.StdEncoding.EncodeToString(b)
if err == nil && fmt.Sprintf("%v", b) == fmt.Sprintf("%v", src) {
t.Fatalf("%v JudgeBase64 Failed, expected true got false", orig)
}
}
})
}
模糊测试结果
> go test -fuzz=FuzzJudgeBase64 -fuzztime 30s
fuzz: elapsed: 0s, gathering baseline coverage: 0/197 completed
fuzz: elapsed: 0s, gathering baseline coverage: 197/197 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 228550 (76179/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 6s, execs: 439994 (70460/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 9s, execs: 621605 (60546/sec), new interesting: 1 (total: 198)
fuzz: elapsed: 12s, execs: 787822 (55397/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 15s, execs: 931280 (47820/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 18s, execs: 1044367 (37695/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 21s, execs: 1149541 (35062/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 24s, execs: 1230256 (26902/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 27s, execs: 1298371 (22711/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 30s, execs: 1348064 (16565/sec), new interesting: 2 (total: 199)
fuzz: elapsed: 31s, execs: 1348064 (0/sec), new interesting: 2 (total: 199)
PASS
ok github.com/tekintian/go-str-utils 32.364s
go-str-utils项目github: GitHub - tekintian/go-str-utils: go语言里面高效,符合使用习惯的字符串相关的实用工具函数 The effective, idiomatic golang utils for go string