Passgen
parent
a9cb361e6b
commit
e0fecbb317
17
go.mod
17
go.mod
|
@ -3,8 +3,10 @@ module code.jhot.me/jhot/hats
|
|||
go 1.21.1
|
||||
|
||||
require (
|
||||
ekyu.moe/base91 v0.2.3
|
||||
github.com/aead/skein v0.0.0-20160722084837-9365ae6e95d2
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/nats-io/nats.go v1.30.2
|
||||
github.com/nats-io/nats.go v1.34.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -24,16 +26,13 @@ require (
|
|||
github.com/go-chi/render v1.0.3
|
||||
github.com/go-co-op/gocron v1.35.2
|
||||
github.com/go-resty/resty/v2 v2.9.1
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd
|
||||
github.com/icholy/digest v0.1.22
|
||||
github.com/klauspost/compress v1.17.0 // indirect
|
||||
github.com/nats-io/nats-server/v2 v2.10.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.5 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/samber/lo v1.38.1
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/crypto v0.18.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
|
42
go.sum
42
go.sum
|
@ -1,3 +1,7 @@
|
|||
ekyu.moe/base91 v0.2.3 h1:1jCZrrpWjDSMMjjU9LANfQV+n7EHeW0OQ0MO9fpDRHg=
|
||||
ekyu.moe/base91 v0.2.3/go.mod h1:/qmmaFUj5d0p9xcpj8beZDj33yXrc54eGU+hO/V5vuo=
|
||||
github.com/aead/skein v0.0.0-20160722084837-9365ae6e95d2 h1:q5TSngwXJdajCyZPQR+eKyRRgI3/ZXC/Nq1ZxZ4Zxu8=
|
||||
github.com/aead/skein v0.0.0-20160722084837-9365ae6e95d2/go.mod h1:4JBZEId5BaLqvA2DGU53phvwkn2WpeLhNSF79/uKBPs=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -16,13 +20,9 @@ github.com/go-co-op/gocron v1.35.2 h1:lG3rdA9TqBBC/PtT2ukQqgLm6jEepnAzz3+OQetvPT
|
|||
github.com/go-co-op/gocron v1.35.2/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
|
||||
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
|
||||
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd h1:PppHBegd3uPZ3Y/Iax/2mlCFJm1w4Qf/zP1MdW4ju2o=
|
||||
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
|
@ -31,24 +31,18 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
|||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/icholy/digest v0.1.22 h1:dRIwCjtAcXch57ei+F0HSb5hmprL873+q7PoVojdMzM=
|
||||
github.com/icholy/digest v0.1.22/go.mod h1:uLAeDdWKIWNFMH0wqbwchbTQOmJWhzSnL7zmqSPqEEc=
|
||||
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/nats-io/jwt/v2 v2.5.2 h1:DhGH+nKt+wIkDxM6qnVSKjokq5t59AZV5HRcFW0zJwU=
|
||||
github.com/nats-io/jwt/v2 v2.5.2/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
|
||||
github.com/nats-io/nats-server/v2 v2.10.2 h1:2o/OOyc/dxeMCQtrF1V/9er0SU0A3LKhDlv/+rqreBM=
|
||||
github.com/nats-io/nats-server/v2 v2.10.2/go.mod h1:lzrskZ/4gyMAh+/66cCd+q74c6v7muBypzfWhP/MAaM=
|
||||
github.com/nats-io/nats.go v1.30.2 h1:aloM0TGpPorZKQhbAkdCzYDj+ZmsJDyeo3Gkbr72NuY=
|
||||
github.com/nats-io/nats.go v1.30.2/go.mod h1:dcfhUgmQNN4GJEfIb2f9R7Fow+gzBF4emzDHrVBd5qM=
|
||||
github.com/nats-io/nkeys v0.4.5 h1:Zdz2BUlFm4fJlierwvGK+yl20IAKUm7eV6AAZXEhkPk=
|
||||
github.com/nats-io/nkeys v0.4.5/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
|
||||
github.com/nats-io/nats.go v1.34.1 h1:syWey5xaNHZgicYBemv0nohUPPmaLteiBEUT6Q5+F/4=
|
||||
github.com/nats-io/nats.go v1.34.1/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
|
||||
github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
|
@ -77,8 +71,8 @@ go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
|
@ -103,8 +97,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
@ -115,8 +109,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -126,11 +121,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"code.jhot.me/jhot/hats/pkg/config"
|
||||
"code.jhot.me/jhot/hats/pkg/homeassistant"
|
||||
ntfyPkg "code.jhot.me/jhot/hats/pkg/ntfy"
|
||||
"code.jhot.me/jhot/hats/pkg/passgen"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
|
@ -72,6 +74,35 @@ func Listen(parentLogger *slog.Logger, parentConfig *config.HatsConfig, readme [
|
|||
render.PlainText(w, r, "OK")
|
||||
})
|
||||
|
||||
router.Get("/passgen", func(w http.ResponseWriter, r *http.Request) {
|
||||
versionString := r.URL.Query().Get("version")
|
||||
salt := r.URL.Query().Get("salt")
|
||||
passphrase := r.URL.Query().Get("passphrase")
|
||||
lengthString := r.URL.Query().Get("length")
|
||||
|
||||
length, err := strconv.ParseInt(lengthString, 10, 64)
|
||||
if err != nil {
|
||||
length = 40
|
||||
}
|
||||
|
||||
version := 2
|
||||
if versionString == "1" {
|
||||
version = 1
|
||||
}
|
||||
|
||||
pg := passgen.NewPasswordGenerator(
|
||||
passgen.WithVersion(version),
|
||||
passgen.WithSalt(salt),
|
||||
passgen.WithPassphrase(passphrase),
|
||||
passgen.WithLength(int(length)))
|
||||
|
||||
if customSpecials := r.URL.Query().Get("specials"); customSpecials != "" {
|
||||
pg.CustomSpecials = customSpecials
|
||||
}
|
||||
|
||||
render.PlainText(w, r, pg.Generate())
|
||||
})
|
||||
|
||||
router.Route("/api", func(r chi.Router) {
|
||||
r.Use(authMiddleware)
|
||||
r.Get(`/state/{entityId}`, getEntityStateHandler)
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package passgen
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
|
||||
"ekyu.moe/base91"
|
||||
"github.com/aead/skein"
|
||||
)
|
||||
|
||||
func (p *Passgen) Generate() string {
|
||||
switch p.Version {
|
||||
case 1:
|
||||
return p.V1()
|
||||
default:
|
||||
return p.V2()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Passgen) V2() string {
|
||||
charset := p.createCharset()
|
||||
var out string
|
||||
|
||||
for i := 0; true; i++ {
|
||||
if len(out) >= p.Length { // password is long enough
|
||||
if p.containsNecessaryCharacters(out[0:p.Length]) { // password contains all necessary character classes
|
||||
break
|
||||
} else { // password is missing character classes, keep second half and keep generating if necessary
|
||||
out = out[int(len(out)/2):]
|
||||
}
|
||||
}
|
||||
|
||||
passphrasePart := strings.Repeat(p.Passphrase, i+1)
|
||||
hash := sha256.Sum256([]byte(passphrasePart + p.Salt))
|
||||
|
||||
// convert hash array of bytes into bigint
|
||||
num := new(big.Int)
|
||||
num.SetString(hex.EncodeToString(hash[:]), 16)
|
||||
// encode bigint in custom character set
|
||||
encoded := encodeToCustomCharset(num, charset)
|
||||
|
||||
out = fmt.Sprintf("%s%s", out, encoded)
|
||||
}
|
||||
|
||||
return out[0:p.Length]
|
||||
}
|
||||
|
||||
func (p *Passgen) createCharset() string {
|
||||
charset := ""
|
||||
if !p.NoUppers {
|
||||
charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
}
|
||||
charset += "abcdefghijklmnopqrstuvwxyz"
|
||||
if !p.NoNumbers {
|
||||
charset += "0123456789"
|
||||
}
|
||||
if !p.NoSpecials {
|
||||
charset += p.CustomSpecials
|
||||
}
|
||||
|
||||
return charset
|
||||
}
|
||||
|
||||
func (p *Passgen) containsNecessaryCharacters(password string) bool {
|
||||
if !p.NoNumbers {
|
||||
if matches, _ := regexp.MatchString("[0-9]", password); !matches {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !p.NoUppers {
|
||||
if matches, _ := regexp.MatchString("[A-Z]", password); !matches {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !p.NoSpecials {
|
||||
customSpecials := p.CustomSpecials
|
||||
escapeChars := []string{"-", "[", "]", "^"}
|
||||
for _, e := range escapeChars {
|
||||
customSpecials = strings.ReplaceAll(customSpecials, e, "\\"+e)
|
||||
}
|
||||
if matches, _ := regexp.MatchString(fmt.Sprintf("[%s]", customSpecials), password); !matches {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if matches, _ := regexp.MatchString("[a-z]", password); !matches {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func encodeToCustomCharset(v *big.Int, charset string) string {
|
||||
charsetLength := big.NewInt(int64(len(charset)))
|
||||
var ret string
|
||||
for {
|
||||
if v.Cmp(big.NewInt(0)) == 0 {
|
||||
break
|
||||
}
|
||||
remainder := new(big.Int).Mod(v, charsetLength)
|
||||
v = new(big.Int).Div(v, charsetLength)
|
||||
|
||||
ret = fmt.Sprintf("%c%s", charset[remainder.Uint64()], ret)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *Passgen) V1() string {
|
||||
encoded := utf16.Encode([]rune(p.Passphrase + p.Salt))
|
||||
var hashOut [64]byte
|
||||
skein.Sum512(&hashOut, convertUTF16ToLittleEndianBytes(encoded), nil)
|
||||
hashHex := hex.EncodeToString(hashOut[:])
|
||||
|
||||
start := hexToInt(string(hashHex[0])) + hexToInt(string(hashHex[1])) + hexToInt(string(hashHex[2]))
|
||||
end := start + p.Length
|
||||
|
||||
encoded91 := base91.EncodeToString([]byte(hashHex))
|
||||
|
||||
for {
|
||||
if len(encoded91) > end {
|
||||
break
|
||||
}
|
||||
encoded91 = encoded91 + encoded91
|
||||
}
|
||||
|
||||
return encoded91[start:end]
|
||||
}
|
||||
|
||||
func convertUTF16ToLittleEndianBytes(u []uint16) []byte {
|
||||
b := make([]byte, 2*len(u))
|
||||
for index, value := range u {
|
||||
binary.LittleEndian.PutUint16(b[index*2:], value)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func hexToInt(hexString string) int {
|
||||
i, _ := strconv.ParseInt(hexString, 16, 0)
|
||||
return int(i)
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package passgen
|
||||
|
||||
type Passgen struct {
|
||||
Version int
|
||||
Passphrase string
|
||||
Salt string
|
||||
Length int
|
||||
NoUppers bool
|
||||
NoNumbers bool
|
||||
NoSpecials bool
|
||||
CustomSpecials string
|
||||
}
|
||||
|
||||
type PassgenOption func(*Passgen)
|
||||
|
||||
func WithVersion(version int) PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.Version = version
|
||||
}
|
||||
}
|
||||
|
||||
func WithPassphrase(passphrase string) PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.Passphrase = passphrase
|
||||
}
|
||||
}
|
||||
|
||||
func WithSalt(salt string) PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.Salt = salt
|
||||
}
|
||||
}
|
||||
|
||||
func WithLength(length int) PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.Length = length
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutSpecials() PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.NoSpecials = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutNumbers() PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.NoNumbers = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutUppers() PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.NoUppers = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomSpecials(specials string) PassgenOption {
|
||||
return func(p *Passgen) {
|
||||
p.CustomSpecials = specials
|
||||
}
|
||||
}
|
||||
|
||||
func NewPasswordGenerator(opts ...PassgenOption) *Passgen {
|
||||
p := &Passgen{
|
||||
Version: 2,
|
||||
Length: 40,
|
||||
NoUppers: false,
|
||||
NoNumbers: false,
|
||||
NoSpecials: false,
|
||||
CustomSpecials: "!@#$%^&*",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
Loading…
Reference in New Issue