mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-21 19:39:43 +01:00
support migrating anope databases
This commit is contained in:
parent
4336f56204
commit
82be9a8423
165
distrib/anope/anope2json.py
Normal file
165
distrib/anope/anope2json.py
Normal file
@ -0,0 +1,165 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import re
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from collections import defaultdict, namedtuple
|
||||
|
||||
AnopeObject = namedtuple('AnopeObject', ('type', 'kv'))
|
||||
|
||||
MASK_MAGIC_REGEX = re.compile(r'[*?!@]')
|
||||
|
||||
def access_level_to_amode(level):
|
||||
try:
|
||||
level = int(level)
|
||||
except:
|
||||
return None
|
||||
if level >= 10000:
|
||||
return 'q'
|
||||
elif level >= 9999:
|
||||
return 'a'
|
||||
elif level >= 5:
|
||||
return 'o'
|
||||
elif level >= 4:
|
||||
return 'h'
|
||||
elif level >= 3:
|
||||
return 'v'
|
||||
else:
|
||||
return None
|
||||
|
||||
def to_unixnano(timestamp):
|
||||
return int(timestamp) * (10**9)
|
||||
|
||||
def file_to_objects(infile):
|
||||
result = []
|
||||
obj = None
|
||||
for line in infile:
|
||||
pieces = line.rstrip('\r\n').split(' ', maxsplit=2)
|
||||
if len(pieces) == 0:
|
||||
logging.warning("skipping blank line in db")
|
||||
continue
|
||||
if pieces[0] == 'END':
|
||||
result.append(obj)
|
||||
obj = None
|
||||
elif pieces[0] == 'OBJECT':
|
||||
obj = AnopeObject(pieces[1], {})
|
||||
elif pieces[0] == 'DATA':
|
||||
obj.kv[pieces[1]] = pieces[2]
|
||||
else:
|
||||
raise ValueError("unknown command found in anope db", pieces[0])
|
||||
return result
|
||||
|
||||
ANOPE_MODENAME_TO_MODE = {
|
||||
'NOEXTERNAL': 'n',
|
||||
'TOPIC': 't',
|
||||
'INVITE': 'i',
|
||||
'NOCTCP': 'C',
|
||||
'AUDITORIUM': 'u',
|
||||
'SECRET': 's',
|
||||
}
|
||||
|
||||
def convert(infile):
|
||||
out = {
|
||||
'version': 1,
|
||||
'source': 'anope',
|
||||
'users': defaultdict(dict),
|
||||
'channels': defaultdict(dict),
|
||||
}
|
||||
|
||||
objects = file_to_objects(infile)
|
||||
|
||||
lastmode_channels = set()
|
||||
|
||||
for obj in objects:
|
||||
if obj.type == 'NickCore':
|
||||
username = obj.kv['display']
|
||||
userdata = {'name': username, 'hash': obj.kv['pass'], 'email': obj.kv['email']}
|
||||
out['users'][username] = userdata
|
||||
elif obj.type == 'NickAlias':
|
||||
username = obj.kv['nc']
|
||||
nick = obj.kv['nick']
|
||||
userdata = out['users'][username]
|
||||
if username.lower() == nick.lower():
|
||||
userdata['registeredAt'] = to_unixnano(obj.kv['time_registered'])
|
||||
else:
|
||||
if 'additionalNicks' not in userdata:
|
||||
userdata['additionalNicks'] = []
|
||||
userdata['additionalNicks'].append(nick)
|
||||
elif obj.type == 'ChannelInfo':
|
||||
chname = obj.kv['name']
|
||||
founder = obj.kv['founder']
|
||||
chdata = {
|
||||
'name': chname,
|
||||
'founder': founder,
|
||||
'registeredAt': to_unixnano(obj.kv['time_registered']),
|
||||
'topic': obj.kv['last_topic'],
|
||||
'topicSetBy': obj.kv['last_topic_setter'],
|
||||
'topicSetAt': to_unixnano(obj.kv['last_topic_time']),
|
||||
'amode': {founder: 'q',}
|
||||
}
|
||||
# DATA last_modes INVITE KEY,hunter2 NOEXTERNAL REGISTERED TOPIC
|
||||
last_modes = obj.kv.get('last_modes')
|
||||
if last_modes:
|
||||
modes = []
|
||||
for mode_desc in last_modes.split():
|
||||
if ',' in mode_desc:
|
||||
mode_name, mode_value = mode_desc.split(',', maxsplit=1)
|
||||
else:
|
||||
mode_name, mode_value = mode_desc, None
|
||||
if mode_name == 'KEY':
|
||||
chdata['key'] = mode_value
|
||||
else:
|
||||
modes.append(ANOPE_MODENAME_TO_MODE.get(mode_name, ''))
|
||||
chdata['modes'] = ''.join(modes)
|
||||
# prevent subsequent ModeLock objects from modifying the mode list further:
|
||||
lastmode_channels.add(chname)
|
||||
out['channels'][chname] = chdata
|
||||
elif obj.type == 'ModeLock':
|
||||
if obj.kv.get('set') != '1':
|
||||
continue
|
||||
chname = obj.kv['ci']
|
||||
if chname in lastmode_channels:
|
||||
continue
|
||||
chdata = out['channels'][chname]
|
||||
modename = obj.kv['name']
|
||||
if modename == 'KEY':
|
||||
chdata['key'] = obj.kv['param']
|
||||
else:
|
||||
oragono_mode = ANOPE_MODENAME_TO_MODE.get(modename)
|
||||
if oragono_mode is not None:
|
||||
stored_modes = chdata.get('modes', '')
|
||||
stored_modes += oragono_mode
|
||||
chdata['modes'] = stored_modes
|
||||
elif obj.type == 'ChanAccess':
|
||||
chname = obj.kv['ci']
|
||||
target = obj.kv['mask']
|
||||
mode = access_level_to_amode(obj.kv['data'])
|
||||
if mode is None:
|
||||
continue
|
||||
if MASK_MAGIC_REGEX.search(target):
|
||||
continue
|
||||
chdata = out['channels'][chname]
|
||||
amode = chdata.setdefault('amode', {})
|
||||
amode[target] = mode
|
||||
chdata['amode'] = amode
|
||||
|
||||
# do some basic integrity checks
|
||||
for chname, chdata in out['channels'].items():
|
||||
founder = chdata.get('founder')
|
||||
if founder not in out['users']:
|
||||
raise ValueError("no user corresponding to channel founder", chname, chdata.get('founder'))
|
||||
|
||||
return out
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
raise Exception("Usage: anope2json.py anope.db output.json")
|
||||
with open(sys.argv[1]) as infile:
|
||||
output = convert(infile)
|
||||
with open(sys.argv[2], 'w') as outfile:
|
||||
json.dump(output, outfile)
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig()
|
||||
sys.exit(main())
|
@ -1,3 +1,5 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
@ -6,6 +8,14 @@ from collections import defaultdict
|
||||
def to_unixnano(timestamp):
|
||||
return int(timestamp) * (10**9)
|
||||
|
||||
# include/atheme/channels.h
|
||||
CMODE_FLAG_TO_MODE = {
|
||||
0x001: 'i', # CMODE_INVITE
|
||||
0x010: 'n', # CMODE_NOEXT
|
||||
0x080: 's', # CMODE_SEC
|
||||
0x100: 't', # CMODE_TOPIC
|
||||
}
|
||||
|
||||
def convert(infile):
|
||||
out = {
|
||||
'version': 1,
|
||||
@ -17,8 +27,8 @@ def convert(infile):
|
||||
channel_to_founder = defaultdict(lambda: (None, None))
|
||||
|
||||
for line in infile:
|
||||
line = line.strip()
|
||||
parts = line.split()
|
||||
line = line.rstrip('\r\n')
|
||||
parts = line.split(' ')
|
||||
category = parts[0]
|
||||
if category == 'MU':
|
||||
# user account
|
||||
@ -43,8 +53,23 @@ def convert(infile):
|
||||
elif category == 'MC':
|
||||
# channel registration
|
||||
# MC #mychannel 1600134478 1600467343 +v 272 0 0
|
||||
# MC #NEWCHANNELTEST 1602270889 1602270974 +vg 1 0 0 jaeger4
|
||||
chname = parts[1]
|
||||
out['channels'][chname].update({'name': chname, 'registeredAt': to_unixnano(parts[2])})
|
||||
chdata = out['channels'][chname]
|
||||
# XXX just give everyone +nt, regardless of lock status; they can fix it later
|
||||
chdata.update({'name': chname, 'registeredAt': to_unixnano(parts[2])})
|
||||
if parts[8] != '':
|
||||
chdata['key'] = parts[8]
|
||||
modes = {'n', 't'}
|
||||
mlock_on, mlock_off = int(parts[5]), int(parts[6])
|
||||
for flag, mode in CMODE_FLAG_TO_MODE.items():
|
||||
if flag & mlock_on != 0:
|
||||
modes.add(mode)
|
||||
for flag, mode in CMODE_FLAG_TO_MODE.items():
|
||||
if flag & mlock_off != 0:
|
||||
modes.remove(mode)
|
||||
chdata['modes'] = ''.join(modes)
|
||||
chdata['limit'] = int(parts[7])
|
||||
elif category == 'MDC':
|
||||
# auxiliary data for a channel registration
|
||||
# MDC #mychannel private:topic:setter s
|
||||
@ -68,6 +93,7 @@ def convert(infile):
|
||||
set_at = int(parts[4])
|
||||
if 'amode' not in chdata:
|
||||
chdata['amode'] = {}
|
||||
# see libathemecore/flags.c: +o is op, +O is autoop, etc.
|
||||
if 'F' in flags:
|
||||
# there can only be one founder
|
||||
preexisting_founder, preexisting_set_at = channel_to_founder[chname]
|
||||
@ -75,15 +101,15 @@ def convert(infile):
|
||||
chdata['founder'] = username
|
||||
channel_to_founder[chname] = (username, set_at)
|
||||
# but multiple people can receive the 'q' amode
|
||||
chdata['amode'][username] = ord('q')
|
||||
elif 'a' in flags:
|
||||
chdata['amode'][username] = ord('a')
|
||||
elif 'o' in flags:
|
||||
chdata['amode'][username] = ord('o')
|
||||
elif 'h' in flags:
|
||||
chdata['amode'][username] = ord('h')
|
||||
elif 'v' in flags:
|
||||
chdata['amode'][username] = ord('v')
|
||||
chdata['amode'][username] = 'q'
|
||||
elif 'q' in flags:
|
||||
chdata['amode'][username] = 'q'
|
||||
elif 'o' in flags or 'O' in flags:
|
||||
chdata['amode'][username] = 'o'
|
||||
elif 'h' in flags or 'H' in flags:
|
||||
chdata['amode'][username] = 'h'
|
||||
elif 'v' in flags or 'V' in flags:
|
||||
chdata['amode'][username] = 'v'
|
||||
else:
|
||||
pass
|
||||
|
||||
@ -92,9 +118,6 @@ def convert(infile):
|
||||
founder = chdata.get('founder')
|
||||
if founder not in out['users']:
|
||||
raise ValueError("no user corresponding to channel founder", chname, chdata.get('founder'))
|
||||
if 'registeredChannels' not in out['users'][founder]:
|
||||
out['users'][founder]['registeredChannels'] = []
|
||||
out['users'][founder]['registeredChannels'].append(chname)
|
||||
|
||||
return out
|
||||
|
||||
|
@ -1056,6 +1056,8 @@ func (am *AccountManager) checkPassphrase(accountName, passphrase string) (accou
|
||||
}
|
||||
case -1:
|
||||
err = am.checkLegacyPassphrase(migrations.CheckAthemePassphrase, accountName, account.Credentials.PassphraseHash, passphrase)
|
||||
case -2:
|
||||
err = am.checkLegacyPassphrase(migrations.CheckAnopePassphrase, accountName, account.Credentials.PassphraseHash, passphrase)
|
||||
default:
|
||||
err = errAccountInvalidCredentials
|
||||
}
|
||||
@ -1899,6 +1901,7 @@ const (
|
||||
CredentialsSHA3Bcrypt CredentialsVersion = 1
|
||||
// negative numbers for migration
|
||||
CredentialsAtheme = -1
|
||||
CredentialsAnope = -2
|
||||
)
|
||||
|
||||
// AccountCredentials stores the various methods for verifying accounts.
|
||||
|
@ -358,11 +358,8 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha
|
||||
|
||||
if includeFlags&IncludeModes != 0 {
|
||||
tx.Set(fmt.Sprintf(keyChannelPassword, channelKey), channelInfo.Key, nil)
|
||||
modeStrings := make([]string, len(channelInfo.Modes))
|
||||
for i, mode := range channelInfo.Modes {
|
||||
modeStrings[i] = string(mode)
|
||||
}
|
||||
tx.Set(fmt.Sprintf(keyChannelModes, channelKey), strings.Join(modeStrings, ""), nil)
|
||||
modeString := modes.Modes(channelInfo.Modes).String()
|
||||
tx.Set(fmt.Sprintf(keyChannelModes, channelKey), modeString, nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelUserLimit, channelKey), strconv.Itoa(channelInfo.UserLimit), nil)
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tidwall/buntdb"
|
||||
|
||||
@ -17,13 +16,12 @@ import (
|
||||
)
|
||||
|
||||
type userImport struct {
|
||||
Name string
|
||||
Hash string
|
||||
Email string
|
||||
RegisteredAt int64 `json:"registeredAt"`
|
||||
Vhost string
|
||||
AdditionalNicks []string `json:"additionalNicks"`
|
||||
RegisteredChannels []string
|
||||
Name string
|
||||
Hash string
|
||||
Email string
|
||||
RegisteredAt int64 `json:"registeredAt"`
|
||||
Vhost string
|
||||
AdditionalNicks []string `json:"additionalNicks"`
|
||||
}
|
||||
|
||||
type channelImport struct {
|
||||
@ -33,7 +31,10 @@ type channelImport struct {
|
||||
Topic string
|
||||
TopicSetBy string `json:"topicSetBy"`
|
||||
TopicSetAt int64 `json:"topicSetAt"`
|
||||
Amode map[string]int
|
||||
Amode map[string]string
|
||||
Modes string
|
||||
Key string
|
||||
Limit int
|
||||
}
|
||||
|
||||
type databaseImport struct {
|
||||
@ -43,7 +44,23 @@ type databaseImport struct {
|
||||
Channels map[string]channelImport
|
||||
}
|
||||
|
||||
func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (err error) {
|
||||
func serializeAmodes(raw map[string]string) (result []byte, err error) {
|
||||
processed := make(map[string]int, len(raw))
|
||||
for accountName, mode := range raw {
|
||||
if len(mode) != 1 {
|
||||
return nil, fmt.Errorf("invalid mode %s for account %s", mode, accountName)
|
||||
}
|
||||
cfname, err := CasefoldName(accountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid amode recipient %s: %w", accountName, err)
|
||||
}
|
||||
processed[cfname] = int(mode[0])
|
||||
}
|
||||
result, err = json.Marshal(processed)
|
||||
return
|
||||
}
|
||||
|
||||
func doImportDBGeneric(config *Config, dbImport databaseImport, credsType CredentialsVersion, tx *buntdb.Tx) (err error) {
|
||||
requiredVersion := 1
|
||||
if dbImport.Version != requiredVersion {
|
||||
return fmt.Errorf("unsupported version of the db for import: version %d is required", requiredVersion)
|
||||
@ -63,7 +80,7 @@ func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (e
|
||||
continue
|
||||
}
|
||||
credentials := AccountCredentials{
|
||||
Version: CredentialsAtheme,
|
||||
Version: credsType,
|
||||
PassphraseHash: []byte(userInfo.Hash),
|
||||
}
|
||||
marshaledCredentials, err := json.Marshal(&credentials)
|
||||
@ -83,9 +100,6 @@ func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (e
|
||||
if len(userInfo.AdditionalNicks) != 0 {
|
||||
tx.Set(fmt.Sprintf(keyAccountAdditionalNicks, cfUsername), marshalReservedNicks(userInfo.AdditionalNicks), nil)
|
||||
}
|
||||
if len(userInfo.RegisteredChannels) != 0 {
|
||||
tx.Set(fmt.Sprintf(keyAccountChannels, cfUsername), strings.Join(userInfo.RegisteredChannels, ","), nil)
|
||||
}
|
||||
}
|
||||
|
||||
for chname, chInfo := range dbImport.Channels {
|
||||
@ -94,23 +108,43 @@ func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (e
|
||||
log.Printf("invalid channel name %s: %v", chname, err)
|
||||
continue
|
||||
}
|
||||
cffounder, err := CasefoldName(chInfo.Founder)
|
||||
if err != nil {
|
||||
log.Printf("invalid founder %s for channel %s: %v", chInfo.Founder, chname, err)
|
||||
continue
|
||||
}
|
||||
tx.Set(fmt.Sprintf(keyChannelExists, cfchname), "1", nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelName, cfchname), chname, nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelRegTime, cfchname), strconv.FormatInt(chInfo.RegisteredAt, 10), nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelFounder, cfchname), chInfo.Founder, nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelFounder, cfchname), cffounder, nil)
|
||||
accountChannelsKey := fmt.Sprintf(keyAccountChannels, cffounder)
|
||||
founderChannels, fcErr := tx.Get(accountChannelsKey)
|
||||
if fcErr != nil || founderChannels == "" {
|
||||
founderChannels = cfchname
|
||||
} else {
|
||||
founderChannels = fmt.Sprintf("%s,%s", founderChannels, cfchname)
|
||||
}
|
||||
tx.Set(accountChannelsKey, founderChannels, nil)
|
||||
if chInfo.Topic != "" {
|
||||
tx.Set(fmt.Sprintf(keyChannelTopic, cfchname), chInfo.Topic, nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelTopicSetTime, cfchname), strconv.FormatInt(chInfo.TopicSetAt, 10), nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelTopicSetBy, cfchname), chInfo.TopicSetBy, nil)
|
||||
}
|
||||
if len(chInfo.Amode) != 0 {
|
||||
m, err := json.Marshal(chInfo.Amode)
|
||||
m, err := serializeAmodes(chInfo.Amode)
|
||||
if err == nil {
|
||||
tx.Set(fmt.Sprintf(keyChannelAccountToUMode, cfchname), string(m), nil)
|
||||
} else {
|
||||
log.Printf("couldn't serialize amodes for %s: %v", chname, err)
|
||||
}
|
||||
}
|
||||
tx.Set(fmt.Sprintf(keyChannelModes, cfchname), chInfo.Modes, nil)
|
||||
if chInfo.Key != "" {
|
||||
tx.Set(fmt.Sprintf(keyChannelPassword, cfchname), chInfo.Key, nil)
|
||||
}
|
||||
if chInfo.Limit > 0 {
|
||||
tx.Set(fmt.Sprintf(keyChannelUserLimit, cfchname), strconv.Itoa(chInfo.Limit), nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -119,9 +153,11 @@ func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (e
|
||||
func doImportDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (err error) {
|
||||
switch dbImport.Source {
|
||||
case "atheme":
|
||||
return doImportAthemeDB(config, dbImport, tx)
|
||||
return doImportDBGeneric(config, dbImport, CredentialsAtheme, tx)
|
||||
case "anope":
|
||||
return doImportDBGeneric(config, dbImport, CredentialsAnope, tx)
|
||||
default:
|
||||
return fmt.Errorf("only imports from atheme are currently supported")
|
||||
return fmt.Errorf("unsupported import source: %s", dbImport.Source)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,14 @@ import (
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
"strconv"
|
||||
|
||||
"github.com/GehirnInc/crypt/md5_crypt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
@ -24,15 +26,18 @@ var (
|
||||
|
||||
hmacServerKeyText = []byte("Server Key")
|
||||
athemePBKDF2V2Prefix = []byte("$z")
|
||||
athemeRawSHA1Prefix = []byte("$rawsha1$")
|
||||
)
|
||||
|
||||
type PassphraseCheck func(hash, passphrase []byte) (err error)
|
||||
|
||||
func CheckAthemePassphrase(hash, passphrase []byte) (err error) {
|
||||
if len(hash) < 60 {
|
||||
return checkAthemePosixCrypt(hash, passphrase)
|
||||
if bytes.HasPrefix(hash, athemeRawSHA1Prefix) {
|
||||
return checkAthemeRawSha1(hash, passphrase)
|
||||
} else if bytes.HasPrefix(hash, athemePBKDF2V2Prefix) {
|
||||
return checkAthemePBKDF2V2(hash, passphrase)
|
||||
} else if len(hash) < 60 {
|
||||
return checkAthemePosixCrypt(hash, passphrase)
|
||||
} else {
|
||||
return checkAthemePBKDF2(hash, passphrase)
|
||||
}
|
||||
@ -181,3 +186,96 @@ func checkAthemePBKDF2(hash, passphrase []byte) (err error) {
|
||||
return ErrHashCheckFailed
|
||||
}
|
||||
}
|
||||
|
||||
func checkAthemeRawSha1(hash, passphrase []byte) (err error) {
|
||||
return checkRawHash(hash[len(athemeRawSHA1Prefix):], passphrase, sha1.New())
|
||||
}
|
||||
|
||||
func checkRawHash(expected, passphrase []byte, h hash.Hash) (err error) {
|
||||
var rawExpected []byte
|
||||
size := h.Size()
|
||||
if len(expected) == 2*size {
|
||||
rawExpected = make([]byte, h.Size())
|
||||
_, err = hex.Decode(rawExpected, expected)
|
||||
if err != nil {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
} else if len(expected) == size {
|
||||
rawExpected = expected
|
||||
} else {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
|
||||
h.Write(passphrase)
|
||||
hashedPassphrase := h.Sum(nil)
|
||||
if subtle.ConstantTimeCompare(rawExpected, hashedPassphrase) == 1 {
|
||||
return nil
|
||||
} else {
|
||||
return ErrHashCheckFailed
|
||||
}
|
||||
}
|
||||
|
||||
func checkAnopeEncSha256(hashBytes, ivBytes, passphrase []byte) (err error) {
|
||||
if len(ivBytes) != 32 {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
// https://github.com/anope/anope/blob/2cf507ed662620d0b97c8484fbfbfa09265e86e1/modules/encryption/enc_sha256.cpp#L67
|
||||
var iv [8]uint32
|
||||
for i := 0; i < 8; i++ {
|
||||
iv[i] = binary.BigEndian.Uint32(ivBytes[i*4 : (i+1)*4])
|
||||
}
|
||||
result := anopeSum256(passphrase, iv)
|
||||
if subtle.ConstantTimeCompare(result[:], hashBytes) == 1 {
|
||||
return nil
|
||||
} else {
|
||||
return ErrHashCheckFailed
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAnopePassphrase(hash, passphrase []byte) (err error) {
|
||||
pieces := bytes.Split(hash, []byte{':'})
|
||||
if len(pieces) < 2 {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
switch string(pieces[0]) {
|
||||
case "plain":
|
||||
// base64, standard encoding
|
||||
expectedPassphrase, err := base64.StdEncoding.DecodeString(string(pieces[1]))
|
||||
if err != nil {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
if subtle.ConstantTimeCompare(passphrase, expectedPassphrase) == 1 {
|
||||
return nil
|
||||
} else {
|
||||
return ErrHashCheckFailed
|
||||
}
|
||||
case "md5":
|
||||
// raw MD5
|
||||
return checkRawHash(pieces[1], passphrase, md5.New())
|
||||
case "sha1":
|
||||
// raw SHA-1
|
||||
return checkRawHash(pieces[1], passphrase, sha1.New())
|
||||
case "bcrypt":
|
||||
if bcrypt.CompareHashAndPassword(pieces[1], passphrase) == nil {
|
||||
return nil
|
||||
} else {
|
||||
return ErrHashCheckFailed
|
||||
}
|
||||
case "sha256":
|
||||
// SHA-256 with an overridden IV
|
||||
if len(pieces) != 3 {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
hashBytes, err := hex.DecodeString(string(pieces[1]))
|
||||
if err != nil {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
ivBytes, err := hex.DecodeString(string(pieces[2]))
|
||||
if err != nil {
|
||||
return ErrHashInvalid
|
||||
}
|
||||
return checkAnopeEncSha256(hashBytes, ivBytes, passphrase)
|
||||
default:
|
||||
return ErrHashInvalid
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
func TestAthemePassphrases(t *testing.T) {
|
||||
var err error
|
||||
|
||||
// modules/crypto/crypt3-md5:
|
||||
err = CheckAthemePassphrase([]byte("$1$hcspif$nCm4r3S14Me9ifsOPGuJT."), []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
@ -21,6 +22,16 @@ func TestAthemePassphrases(t *testing.T) {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
err = CheckAthemePassphrase([]byte("$1$diwesm$9MjapdOyhyC.2FdHzKMzK."), []byte("1Ss1GN4q-3e8SgIJblfQxw"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAthemePassphrase([]byte("$1$hcspif$nCm4r3S14Me9ifsOPGuJT."), []byte("sh1varampassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
// modules/crypto/pbkdf2:
|
||||
err = CheckAthemePassphrase([]byte("khMlbBBIFya2ihyN42abc3e768663e2c4fd0e0020e46292bf9fdf44e9a51d2a2e69509cb73b4b1bf9c1b6355a1fc9ea663fcd6da902287159494f15b905e5e651d6a60f2ec834598"), []byte("password"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
@ -31,6 +42,7 @@ func TestAthemePassphrases(t *testing.T) {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
// modules/crypto/pbkdf2v2:
|
||||
err = CheckAthemePassphrase([]byte("$z$65$64000$1kz1I9YJPJ2gkJALbrpL2DoxRDhYPBOg60KNJMK/6do=$Cnfg6pYhBNrVXiaXYH46byrC+3HKet/XvYwvI1BvZbs=$m0hrT33gcF90n2TU3lm8tdm9V9XC4xEV13KsjuT38iY="), []byte("password"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
@ -40,6 +52,30 @@ func TestAthemePassphrases(t *testing.T) {
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
weirdHash := []byte("$z$6$64000$rWfIGzPY9qiIt7m5$VdFroDOlTQSLlFUJtpvlbp2i7sH3ZUndqwdnOvoDvt6b2AzLjaAK/lhSO/QaR2nA3Wm4ObHdl3WMW32NdtSMdw==")
|
||||
err = CheckAthemePassphrase(weirdHash, []byte("pHQpwje5CjS3_Lx0RaeS7w"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAthemePassphrase(weirdHash, []byte("pHQpwje5CjS3-Lx0RaeS7w"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAthemeRawSha1(t *testing.T) {
|
||||
var err error
|
||||
|
||||
shivaramHash := []byte("$rawsha1$49fffa5543f21dd6effe88a79633e4073e36a828")
|
||||
err = CheckAthemePassphrase(shivaramHash, []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAthemePassphrase(shivaramHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOragonoLegacyPassphrase(t *testing.T) {
|
||||
@ -70,3 +106,109 @@ func TestOragonoLegacyPassphrase(t *testing.T) {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnopePassphraseRawSha1(t *testing.T) {
|
||||
var err error
|
||||
shivaramHash := []byte("sha1:49fffa5543f21dd6effe88a79633e4073e36a828")
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
edHash := []byte("sha1:ea44e256819de972c25fef0aa277396067d6024f")
|
||||
err = CheckAnopePassphrase(edHash, []byte("edpassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(edHash, []byte("shivarampassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnopePassphraseRawMd5(t *testing.T) {
|
||||
var err error
|
||||
shivaramHash := []byte("md5:ce4bd864f37ffaa1b871aef22eea82ff")
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
edHash := []byte("md5:dbf8be80e8dccdd33915b482e4390426")
|
||||
err = CheckAnopePassphrase(edHash, []byte("edpassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(edHash, []byte("shivarampassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnopePassphrasePlain(t *testing.T) {
|
||||
var err error
|
||||
// not actually a hash
|
||||
weirdHash := []byte("plain:YVxzMC1fMmZ+ZjM0OEAhN2FzZGYxNDJAIyFhZmE=")
|
||||
err = CheckAnopePassphrase(weirdHash, []byte("a\\s0-_2f~f348@!7asdf142@#!afa"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(weirdHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnopePassphraseBcrypt(t *testing.T) {
|
||||
var err error
|
||||
shivaramHash := []byte("bcrypt:$2a$10$UyNgHyniPukGf/3A6vzBx.VMNfej0h4WzATg4ahKW2H86a0QLcVIK")
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnopePassphraseEncSha256(t *testing.T) {
|
||||
var err error
|
||||
shivaramHash := []byte("sha256:ff337943c8c4219cd330a3075a699492e0f8b1a823bb76af0129f1f117ba0630:60250c3053f7b34e35576fc5063b8b396fe7b9ab416842117991a8e027aa72f6")
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("shivarampassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(shivaramHash, []byte("edpassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
edHash := []byte("sha256:93a430c8c3c6917dc6e9a32ac1aba90bc5768265278a45b86eacd636fc723d8f:10ea72683a499c155d72cd3571cb80e5050280620f789a44492c0e0c7956942f")
|
||||
err = CheckAnopePassphrase(edHash, []byte("edpassphrase"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(edHash, []byte("shivarampassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
|
||||
weirdHash := []byte("sha256:06d11a06025354e37a7ddf48913a1c9831ffab47d04e4c22a89fd7835abcb6cc:3137788c2749da0419bc9df320991d2d72495c7065da4f39004fd21710601409")
|
||||
err = CheckAnopePassphrase(weirdHash, []byte("1Ss1GN4q-3e8SgIJblfQxw"))
|
||||
if err != nil {
|
||||
t.Errorf("failed to check passphrase: %v", err)
|
||||
}
|
||||
err = CheckAnopePassphrase(weirdHash, []byte("shivarampassphrase"))
|
||||
if err == nil {
|
||||
t.Errorf("accepted invalid passphrase")
|
||||
}
|
||||
}
|
||||
|
128
irc/migrations/sha256.go
Normal file
128
irc/migrations/sha256.go
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// SHA256 implementation from golang/go, modified to accommodate anope's
|
||||
// password hashing scheme, which overrides the initialization vector
|
||||
// using the salt.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// The size of a SHA256 checksum in bytes.
|
||||
const Size = 32
|
||||
|
||||
const (
|
||||
chunk = 64
|
||||
)
|
||||
|
||||
// digest represents the partial evaluation of a checksum.
|
||||
type digest struct {
|
||||
h [8]uint32
|
||||
x [chunk]byte
|
||||
nx int
|
||||
len uint64
|
||||
}
|
||||
|
||||
func (d *digest) Write(p []byte) (nn int, err error) {
|
||||
nn = len(p)
|
||||
d.len += uint64(nn)
|
||||
if d.nx > 0 {
|
||||
n := copy(d.x[d.nx:], p)
|
||||
d.nx += n
|
||||
if d.nx == chunk {
|
||||
sha256BlockGeneric(d, d.x[:])
|
||||
d.nx = 0
|
||||
}
|
||||
p = p[n:]
|
||||
}
|
||||
if len(p) >= chunk {
|
||||
n := len(p) &^ (chunk - 1)
|
||||
sha256BlockGeneric(d, p[:n])
|
||||
p = p[n:]
|
||||
}
|
||||
if len(p) > 0 {
|
||||
d.nx = copy(d.x[:], p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *digest) Sum(in []byte) []byte {
|
||||
// Make a copy of d so that caller can keep writing and summing.
|
||||
d0 := *d
|
||||
hash := d0.checkSum()
|
||||
return append(in, hash[:]...)
|
||||
}
|
||||
|
||||
func (d *digest) checkSum() [Size]byte {
|
||||
len := d.len
|
||||
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
|
||||
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
|
||||
binary.BigEndian.PutUint64(tmp[:], len)
|
||||
d.Write(tmp[0:8])
|
||||
|
||||
if d.nx != 0 {
|
||||
panic("d.nx != 0")
|
||||
}
|
||||
|
||||
var digest [Size]byte
|
||||
|
||||
binary.BigEndian.PutUint32(digest[0:], d.h[0])
|
||||
binary.BigEndian.PutUint32(digest[4:], d.h[1])
|
||||
binary.BigEndian.PutUint32(digest[8:], d.h[2])
|
||||
binary.BigEndian.PutUint32(digest[12:], d.h[3])
|
||||
binary.BigEndian.PutUint32(digest[16:], d.h[4])
|
||||
binary.BigEndian.PutUint32(digest[20:], d.h[5])
|
||||
binary.BigEndian.PutUint32(digest[24:], d.h[6])
|
||||
binary.BigEndian.PutUint32(digest[28:], d.h[7])
|
||||
|
||||
return digest
|
||||
}
|
||||
|
||||
// Anope password hashing function: SHA-256 with an override for the IV
|
||||
// The actual SHA-256 IV for reference:
|
||||
// [8]uint32{0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19}
|
||||
func anopeSum256(data []byte, iv [8]uint32) [Size]byte {
|
||||
var d digest
|
||||
d.h = iv
|
||||
d.Write(data)
|
||||
return d.checkSum()
|
||||
}
|
154
irc/migrations/sha256block.go
Normal file
154
irc/migrations/sha256block.go
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// SHA256 block step.
|
||||
// In its own file so that a faster assembly or C version
|
||||
// can be substituted easily.
|
||||
|
||||
package migrations
|
||||
|
||||
import "math/bits"
|
||||
|
||||
var _K = []uint32{
|
||||
0x428a2f98,
|
||||
0x71374491,
|
||||
0xb5c0fbcf,
|
||||
0xe9b5dba5,
|
||||
0x3956c25b,
|
||||
0x59f111f1,
|
||||
0x923f82a4,
|
||||
0xab1c5ed5,
|
||||
0xd807aa98,
|
||||
0x12835b01,
|
||||
0x243185be,
|
||||
0x550c7dc3,
|
||||
0x72be5d74,
|
||||
0x80deb1fe,
|
||||
0x9bdc06a7,
|
||||
0xc19bf174,
|
||||
0xe49b69c1,
|
||||
0xefbe4786,
|
||||
0x0fc19dc6,
|
||||
0x240ca1cc,
|
||||
0x2de92c6f,
|
||||
0x4a7484aa,
|
||||
0x5cb0a9dc,
|
||||
0x76f988da,
|
||||
0x983e5152,
|
||||
0xa831c66d,
|
||||
0xb00327c8,
|
||||
0xbf597fc7,
|
||||
0xc6e00bf3,
|
||||
0xd5a79147,
|
||||
0x06ca6351,
|
||||
0x14292967,
|
||||
0x27b70a85,
|
||||
0x2e1b2138,
|
||||
0x4d2c6dfc,
|
||||
0x53380d13,
|
||||
0x650a7354,
|
||||
0x766a0abb,
|
||||
0x81c2c92e,
|
||||
0x92722c85,
|
||||
0xa2bfe8a1,
|
||||
0xa81a664b,
|
||||
0xc24b8b70,
|
||||
0xc76c51a3,
|
||||
0xd192e819,
|
||||
0xd6990624,
|
||||
0xf40e3585,
|
||||
0x106aa070,
|
||||
0x19a4c116,
|
||||
0x1e376c08,
|
||||
0x2748774c,
|
||||
0x34b0bcb5,
|
||||
0x391c0cb3,
|
||||
0x4ed8aa4a,
|
||||
0x5b9cca4f,
|
||||
0x682e6ff3,
|
||||
0x748f82ee,
|
||||
0x78a5636f,
|
||||
0x84c87814,
|
||||
0x8cc70208,
|
||||
0x90befffa,
|
||||
0xa4506ceb,
|
||||
0xbef9a3f7,
|
||||
0xc67178f2,
|
||||
}
|
||||
|
||||
func sha256BlockGeneric(dig *digest, p []byte) {
|
||||
var w [64]uint32
|
||||
h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7]
|
||||
for len(p) >= chunk {
|
||||
// Can interlace the computation of w with the
|
||||
// rounds below if needed for speed.
|
||||
for i := 0; i < 16; i++ {
|
||||
j := i * 4
|
||||
w[i] = uint32(p[j])<<24 | uint32(p[j+1])<<16 | uint32(p[j+2])<<8 | uint32(p[j+3])
|
||||
}
|
||||
for i := 16; i < 64; i++ {
|
||||
v1 := w[i-2]
|
||||
t1 := (bits.RotateLeft32(v1, -17)) ^ (bits.RotateLeft32(v1, -19)) ^ (v1 >> 10)
|
||||
v2 := w[i-15]
|
||||
t2 := (bits.RotateLeft32(v2, -7)) ^ (bits.RotateLeft32(v2, -18)) ^ (v2 >> 3)
|
||||
w[i] = t1 + w[i-7] + t2 + w[i-16]
|
||||
}
|
||||
|
||||
a, b, c, d, e, f, g, h := h0, h1, h2, h3, h4, h5, h6, h7
|
||||
|
||||
for i := 0; i < 64; i++ {
|
||||
t1 := h + ((bits.RotateLeft32(e, -6)) ^ (bits.RotateLeft32(e, -11)) ^ (bits.RotateLeft32(e, -25))) + ((e & f) ^ (^e & g)) + _K[i] + w[i]
|
||||
|
||||
t2 := ((bits.RotateLeft32(a, -2)) ^ (bits.RotateLeft32(a, -13)) ^ (bits.RotateLeft32(a, -22))) + ((a & b) ^ (a & c) ^ (b & c))
|
||||
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = d + t1
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = t1 + t2
|
||||
}
|
||||
|
||||
h0 += a
|
||||
h1 += b
|
||||
h2 += c
|
||||
h3 += d
|
||||
h4 += e
|
||||
h5 += f
|
||||
h6 += g
|
||||
h7 += h
|
||||
|
||||
p = p[chunk:]
|
||||
}
|
||||
|
||||
dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] = h0, h1, h2, h3, h4, h5, h6, h7
|
||||
}
|
@ -89,11 +89,11 @@ func (changes ModeChanges) Strings() (result []string) {
|
||||
type Modes []Mode
|
||||
|
||||
func (modes Modes) String() string {
|
||||
strs := make([]string, len(modes))
|
||||
for index, mode := range modes {
|
||||
strs[index] = mode.String()
|
||||
var builder strings.Builder
|
||||
for _, m := range modes {
|
||||
builder.WriteRune(rune(m))
|
||||
}
|
||||
return strings.Join(strs, "")
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
// User Modes
|
||||
|
Loading…
Reference in New Issue
Block a user