tordam

A library for peer discovery inside the Tor network
git clone https://git.parazyd.org/tordam
Log | Files | Refs | README | LICENSE

commit 0ebc557267e12c3544b0833c6c98a2ea335a58fb
parent 2e02e951df8b4bb89560d1fdc0116d9231290d63
Author: parazyd <parazyd@dyne.org>
Date:   Sun, 10 Dec 2017 17:02:45 +0100

Rename lib to damlib and separate functions into more files

Diffstat:
Mcmd/dam-client/main.go | 2+-
Mcmd/dam-dir/main.go | 2+-
Apkg/damlib/crypto.go | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/damlib/helpers.go | 15+++++++++++++++
Apkg/damlib/net.go | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apkg/damlib/tor.go | 32++++++++++++++++++++++++++++++++
Apkg/damlib/validate.go | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpkg/lib/crypto.go | 197-------------------------------------------------------------------------------
Dpkg/lib/helpers.go | 172-------------------------------------------------------------------------------
9 files changed, 395 insertions(+), 371 deletions(-)

diff --git a/cmd/dam-client/main.go b/cmd/dam-client/main.go @@ -13,7 +13,7 @@ import ( "sync" "time" - "github.com/parazyd/tor-dam/pkg/lib" + lib "github.com/parazyd/tor-dam/pkg/damlib" ) // Cwd holds the path to the directory where we will Chdir on startup. diff --git a/cmd/dam-dir/main.go b/cmd/dam-dir/main.go @@ -13,7 +13,7 @@ import ( "time" "github.com/go-redis/redis" - "github.com/parazyd/tor-dam/pkg/lib" + lib "github.com/parazyd/tor-dam/pkg/damlib" ) // Cwd holds the path to the directory where we will Chdir on startup. diff --git a/pkg/damlib/crypto.go b/pkg/damlib/crypto.go @@ -0,0 +1,218 @@ +package damlib + +// See LICENSE file for copyright and license details. + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha512" + "crypto/x509" + "encoding/asn1" + "encoding/base32" + "encoding/pem" + "errors" + "io/ioutil" + "log" + "math/big" + "os" + "strings" +) + +// GenRsa generates a private RSA keypair of a given bitSize int and returns it +// as rsa.PrivateKey. +func GenRsa(bitSize int) (*rsa.PrivateKey, error) { + log.Printf("Generating %d-bit RSA keypair...\n", bitSize) + rng := rand.Reader + key, err := rsa.GenerateKey(rng, bitSize) + if err != nil { + return nil, err + } + return key, nil +} + +// SavePubRsa saves a given RSA public key to a given filename. +// SavePubRsa takes the filename to write as a string, and the key as +// rsa.PublicKey. It returns a boolean value and an error, depending on whether +// it has failed or not. +func SavePubRsa(filename string, pubkey rsa.PublicKey) (bool, error) { + log.Printf("Writing pubkey to %s\n", filename) + // FIXME: worry or not about creating the path if it doesn't exist? + outfile, err := os.Create(filename) + defer outfile.Close() + if err != nil { + return false, err + } + asn1Bytes, err := asn1.Marshal(pubkey) + if err != nil { + return false, err + } + var pemkey = &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: asn1Bytes, + } + err = pem.Encode(outfile, pemkey) + if err != nil { + return false, err + } + err = outfile.Chmod(0400) + if err != nil { + return false, err + } + return true, nil +} + +// SavePrivRsa saves a given RSA private key to a given filename. +// SavePrivRsa takes the filename to write as a string, and the key as +// *rsa.PrivateKey. It returns a boolean value and an error, depending on whether +// it has failed or not. +func SavePrivRsa(filename string, privkey *rsa.PrivateKey) (bool, error) { + log.Printf("Writing private key to %s\n", filename) + // FIXME: worry or not about creating the path if it doesn't exist? + outfile, err := os.Create(filename) + defer outfile.Close() + if err != nil { + return false, err + } + var pemkey = &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privkey), + } + err = pem.Encode(outfile, pemkey) + if err != nil { + return false, err + } + err = outfile.Chmod(0400) + if err != nil { + return false, err + } + return true, nil +} + +// LoadRsaKeyFromFile loads a RSA private key from a given filename. +// LoadRsaKeyFromFile takes a string filename and tries to read from it, parsing +// the private RSA key. It will return a *rsa.PrivateKey on success, and error +// on fail. +func LoadRsaKeyFromFile(filename string) (*rsa.PrivateKey, error) { + log.Println("Loading RSA private key from", filename) + dat, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + block, _ := pem.Decode(dat) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return priv, nil +} + +// SignMsgRsa signs a given []byte message using a given RSA private key. +// It will return the signature as a slice of bytes on success, and error on +// failure. +func SignMsgRsa(message []byte, privkey *rsa.PrivateKey) ([]byte, error) { + log.Println("Signing message...") + rng := rand.Reader + hashed := sha512.Sum512(message) + sig, err := rsa.SignPKCS1v15(rng, privkey, crypto.SHA512, hashed[:]) + if err != nil { + return nil, err + } + return sig, nil +} + +// EncryptMsgRsa encrypts a given []byte message using a given RSA public key. +// Returns the encrypted message as a slice of bytes on success, and error on +// failure. +func EncryptMsgRsa(message []byte, pubkey *rsa.PublicKey) ([]byte, error) { + log.Println("Encrypting message...") + rng := rand.Reader + msg, err := rsa.EncryptPKCS1v15(rng, pubkey, message) + if err != nil { + return nil, err + } + return msg, nil +} + +// DecryptMsgRsa decrypts a given []byte message using a given RSA private key. +// Returns the decrypted message as a slice of bytes on success, and error on +// failure. +func DecryptMsgRsa(message []byte, privkey *rsa.PrivateKey) ([]byte, error) { + log.Println("Decrypting message...") + rng := rand.Reader + msg, err := rsa.DecryptPKCS1v15(rng, privkey, message) + if err != nil { + return nil, err + } + return msg, nil +} + +// VerifyMsgRsa verifies a message and signature against a given RSA pubkey. +// Returns a boolean value and error depending on whether it has failed or not. +func VerifyMsgRsa(message []byte, signature []byte, pubkey *rsa.PublicKey) (bool, error) { + log.Println("Verifying message signature") + hashed := sha512.Sum512(message) + err := rsa.VerifyPKCS1v15(pubkey, crypto.SHA512, hashed[:], signature) + if err != nil { + return false, err + } + log.Println("Signature valid") + return true, nil +} + +// OnionFromPubkeyRsa generates a valid onion address from a given RSA pubkey. +// Returns the onion address as a slice of bytes on success and error on +// failure. +func OnionFromPubkeyRsa(pubkey rsa.PublicKey) ([]byte, error) { + asn1Bytes, err := asn1.Marshal(pubkey) + if err != nil { + return nil, err + } + hashed := sha1.New() + _, err = hashed.Write(asn1Bytes) + if err != nil { + return nil, err + } + encoded := strings.ToLower(base32.StdEncoding.EncodeToString(hashed.Sum(nil)))[:16] + encoded += ".onion" + + return []byte(encoded), nil +} + +// ParsePubkeyRsa parses a []byte form of a RSA public key and returns it as +// *rsa.PublicKey on success. Otherwise, error. +func ParsePubkeyRsa(pubkey []byte) (*rsa.PublicKey, error) { + var pub rsa.PublicKey + var ret *rsa.PublicKey + block, _ := pem.Decode(pubkey) + _, err := asn1.Unmarshal(block.Bytes, &pub) + if err != nil { + return nil, err + } + ret = &pub + return ret, nil +} + +// GenRandomASCII generates a random ASCII string of a given length. +// Takes length int as argument, and returns a string of that length on success +// and error on failure. +func GenRandomASCII(length int) (string, error) { + var res string + for { + if len(res) >= length { + return res, nil + } + num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) + if err != nil { + return "", err + } + n := num.Int64() + if n > 32 && n < 127 { + res += string(n) + } + } +} diff --git a/pkg/damlib/helpers.go b/pkg/damlib/helpers.go @@ -0,0 +1,15 @@ +package damlib + +// See LICENSE file for copyright and license details. + +import ( + "log" +) + +// CheckError is a handler for errors. It takes an error type as an argument, +// and issues a log.Fatalln, printing the error and exiting with os.Exit(1). +func CheckError(err error) { + if err != nil { + log.Fatalln(err) + } +} diff --git a/pkg/damlib/net.go b/pkg/damlib/net.go @@ -0,0 +1,55 @@ +package damlib + +// See LICENSE file for copyright and license details. + +import ( + "bytes" + "log" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/proxy" +) + +// ProxyAddr is the address of our Tor SOCKS port. +const ProxyAddr = "127.0.0.1:9050" + +// HTTPPost sends an HTTP POST request to the given host. +// Takes the host to request and the data to post as arguments. +// If the host ends with ".onion", it will enable the request to be performed +// over a SOCKS proxy, defined in ProxyAddr. +// On success, it will return the http.Response. Otherwise, it returns an error. +func HTTPPost(host string, data []byte) (*http.Response, error) { + socksify := false + parsedHost, err := url.Parse(host) + if err != nil { + return nil, err + } + hostname := parsedHost.Hostname() + if strings.HasSuffix(hostname, ".onion") { + socksify = true + } + httpTransp := &http.Transport{} + httpClient := &http.Client{Transport: httpTransp} + if socksify { + log.Println("Detected a .onion request. Using SOCKS proxy.") + dialer, err := proxy.SOCKS5("tcp", ProxyAddr, nil, proxy.Direct) + if err != nil { + return nil, err + } + httpTransp.Dial = dialer.Dial + } + request, err := http.NewRequest("POST", host, bytes.NewBuffer(data)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(request) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/damlib/tor.go b/pkg/damlib/tor.go @@ -0,0 +1,32 @@ +package damlib + +// See LICENSE file for copyright and license details. + +import ( + "bytes" + "log" + "os/exec" +) + +// FetchHSPubkey fetches a hidden service's RSA pubkey by running an external +// program, giving it an onion address. It returns the retrieved public key as a +// string. +func FetchHSPubkey(addr string) string { + var outb, errb bytes.Buffer + + log.Println("Fetching pubkey for:", addr) + + cmd := exec.Command("damauth.py", addr) + cmd.Stdout = &outb + cmd.Stderr = &errb + err := cmd.Start() + CheckError(err) + + err = cmd.Wait() + if err != nil { + log.Println("Could not fetch descriptor:", err) + return "" + } + + return outb.String() +} diff --git a/pkg/damlib/validate.go b/pkg/damlib/validate.go @@ -0,0 +1,73 @@ +package damlib + +// See LICENSE file for copyright and license details. + +import ( + "log" + "regexp" + "strings" + "time" +) + +// ValidateReq validates our given request against the logic we are checking. +// The function takes a request data map, and a public key in the form of a +// string. If the public key is an empty string, the function will run an +// external program to fetch the node's public key from a Tor HSDir. +// +// ValidateReq will first validate "nodetype", looking whether the announcer +// is a node or a directory. +// Then, it will validate the onion address using a regular expression. +// Now, if pubkey is empty, it will run the external program to fetch it. If a +// descriptor can't be retrieved, it will retry for 10 times, and fail if those +// are not successful. +// +// Continuing, ValidateReq will verify the RSA signature posted by the +// announcer. +// If any of the above are invalid, the function will return nil and false. +// Otherwise, it will return the pubkey as a slice of bytes, and true. +func ValidateReq(req map[string]string, pubkey string) ([]byte, bool) { + // Validate nodetype. + if req["nodetype"] != "node" { + return nil, false + } + // Validate address. + re, err := regexp.Compile("^[a-z2-7]{16}\\.onion$") + CheckError(err) + if len(re.FindString(req["address"])) != 22 { + return nil, false + } + log.Println(req["address"], "seems valid") + + if len(pubkey) == 0 { + // Address is valid, we try to fetch its pubkey from a HSDir + cnt := 0 + for { // We try until we have it. + cnt++ + if cnt > 10 { + // We probably can't get a good HSDir. The client shall retry + // later on. + return []byte("Couldn't get a descriptor. Try later."), false + } + pubkey = FetchHSPubkey(req["address"]) + if strings.HasPrefix(pubkey, "-----BEGIN RSA PUBLIC KEY-----") && + strings.HasSuffix(pubkey, "-----END RSA PUBLIC KEY-----") { + log.Println("Got descriptor!") + break + } + time.Sleep(2000 * time.Millisecond) + } + } + // Validate signature. + msg := []byte(req["message"]) + sig := []byte(req["signature"]) + pub, err := ParsePubkeyRsa([]byte(pubkey)) + CheckError(err) + + val, err := VerifyMsgRsa(msg, sig, pub) + CheckError(err) + if val != true { + return nil, false + } + + return []byte(pubkey), true +} diff --git a/pkg/lib/crypto.go b/pkg/lib/crypto.go @@ -1,197 +0,0 @@ -package lib - -// See LICENSE file for copyright and license details. - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/sha512" - "crypto/x509" - "encoding/asn1" - "encoding/base32" - "encoding/pem" - "errors" - "io/ioutil" - "log" - "os" - "strings" -) - -// GenRsa generates a private RSA keypair of a given bitSize int and returns it -// as rsa.PrivateKey. -func GenRsa(bitSize int) (*rsa.PrivateKey, error) { - log.Printf("Generating %d-bit RSA keypair...\n", bitSize) - rng := rand.Reader - key, err := rsa.GenerateKey(rng, bitSize) - if err != nil { - return nil, err - } - return key, nil -} - -// SavePubRsa saves a given RSA public key to a given filename. -// SavePubRsa takes the filename to write as a string, and the key as -// rsa.PublicKey. It returns a boolean value and an error, depending on whether -// it has failed or not. -func SavePubRsa(filename string, pubkey rsa.PublicKey) (bool, error) { - log.Printf("Writing pubkey to %s\n", filename) - // FIXME: worry or not about creating the path if it doesn't exist? - outfile, err := os.Create(filename) - defer outfile.Close() - if err != nil { - return false, err - } - asn1Bytes, err := asn1.Marshal(pubkey) - if err != nil { - return false, err - } - var pemkey = &pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: asn1Bytes, - } - err = pem.Encode(outfile, pemkey) - if err != nil { - return false, err - } - err = outfile.Chmod(0400) - if err != nil { - return false, err - } - return true, nil -} - -// SavePrivRsa saves a given RSA private key to a given filename. -// SavePrivRsa takes the filename to write as a string, and the key as -// *rsa.PrivateKey. It returns a boolean value and an error, depending on whether -// it has failed or not. -func SavePrivRsa(filename string, privkey *rsa.PrivateKey) (bool, error) { - log.Printf("Writing private key to %s\n", filename) - // FIXME: worry or not about creating the path if it doesn't exist? - outfile, err := os.Create(filename) - defer outfile.Close() - if err != nil { - return false, err - } - var pemkey = &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(privkey), - } - err = pem.Encode(outfile, pemkey) - if err != nil { - return false, err - } - err = outfile.Chmod(0400) - if err != nil { - return false, err - } - return true, nil -} - -// LoadRsaKeyFromFile loads a RSA private key from a given filename. -// LoadRsaKeyFromFile takes a string filename and tries to read from it, parsing -// the private RSA key. It will return a *rsa.PrivateKey on success, and error -// on fail. -func LoadRsaKeyFromFile(filename string) (*rsa.PrivateKey, error) { - log.Println("Loading RSA private key from", filename) - dat, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - block, _ := pem.Decode(dat) - if block == nil { - return nil, errors.New("failed to parse PEM block containing the key") - } - priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, err - } - return priv, nil -} - -// SignMsgRsa signs a given []byte message using a given RSA private key. -// It will return the signature as a slice of bytes on success, and error on -// failure. -func SignMsgRsa(message []byte, privkey *rsa.PrivateKey) ([]byte, error) { - log.Println("Signing message...") - rng := rand.Reader - hashed := sha512.Sum512(message) - sig, err := rsa.SignPKCS1v15(rng, privkey, crypto.SHA512, hashed[:]) - if err != nil { - return nil, err - } - return sig, nil -} - -// EncryptMsgRsa encrypts a given []byte message using a given RSA public key. -// Returns the encrypted message as a slice of bytes on success, and error on -// failure. -func EncryptMsgRsa(message []byte, pubkey *rsa.PublicKey) ([]byte, error) { - log.Println("Encrypting message...") - rng := rand.Reader - msg, err := rsa.EncryptPKCS1v15(rng, pubkey, message) - if err != nil { - return nil, err - } - return msg, nil -} - -// DecryptMsgRsa decrypts a given []byte message using a given RSA private key. -// Returns the decrypted message as a slice of bytes on success, and error on -// failure. -func DecryptMsgRsa(message []byte, privkey *rsa.PrivateKey) ([]byte, error) { - log.Println("Decrypting message...") - rng := rand.Reader - msg, err := rsa.DecryptPKCS1v15(rng, privkey, message) - if err != nil { - return nil, err - } - return msg, nil -} - -// VerifyMsgRsa verifies a message and signature against a given RSA pubkey. -// Returns a boolean value and error depending on whether it has failed or not. -func VerifyMsgRsa(message []byte, signature []byte, pubkey *rsa.PublicKey) (bool, error) { - log.Println("Verifying message signature") - hashed := sha512.Sum512(message) - err := rsa.VerifyPKCS1v15(pubkey, crypto.SHA512, hashed[:], signature) - if err != nil { - return false, err - } - log.Println("Signature valid") - return true, nil -} - -// OnionFromPubkeyRsa generates a valid onion address from a given RSA pubkey. -// Returns the onion address as a slice of bytes on success and error on -// failure. -func OnionFromPubkeyRsa(pubkey rsa.PublicKey) ([]byte, error) { - asn1Bytes, err := asn1.Marshal(pubkey) - if err != nil { - return nil, err - } - hashed := sha1.New() - _, err = hashed.Write(asn1Bytes) - if err != nil { - return nil, err - } - encoded := strings.ToLower(base32.StdEncoding.EncodeToString(hashed.Sum(nil)))[:16] - encoded += ".onion" - - return []byte(encoded), nil -} - -// ParsePubkeyRsa parses a []byte form of a RSA public key and returns it as -// *rsa.PublicKey on success. Otherwise, error. -func ParsePubkeyRsa(pubkey []byte) (*rsa.PublicKey, error) { - var pub rsa.PublicKey - var ret *rsa.PublicKey - block, _ := pem.Decode(pubkey) - _, err := asn1.Unmarshal(block.Bytes, &pub) - if err != nil { - return nil, err - } - ret = &pub - return ret, nil -} diff --git a/pkg/lib/helpers.go b/pkg/lib/helpers.go @@ -1,172 +0,0 @@ -package lib - -// See LICENSE file for copyright and license details. - -import ( - "bytes" - "crypto/rand" - "log" - "math/big" - "net/http" - "net/url" - "os/exec" - "regexp" - "strings" - "time" - - "golang.org/x/net/proxy" -) - -// ProxyAddr is the address of our Tor SOCKS port. -const ProxyAddr = "127.0.0.1:9050" - -// CheckError is a handler for errors. It takes an error type as an argument, -// and issues a log.Fatalln, printing the error and exiting with os.Exit(1). -func CheckError(err error) { - if err != nil { - log.Fatalln(err) - } -} - -// FetchHSPubkey fetches a hidden service's RSA pubkey by running an external -// program, giving it an onion address. It returns the retrieved public key as a -// string. -func FetchHSPubkey(addr string) string { - var outb, errb bytes.Buffer - - log.Println("Fetching pubkey for:", addr) - - cmd := exec.Command("damauth.py", addr) - cmd.Stdout = &outb - cmd.Stderr = &errb - err := cmd.Start() - CheckError(err) - - err = cmd.Wait() - if err != nil { - log.Println("Could not fetch descriptor:", err) - return "" - } - - return outb.String() -} - -// ValidateReq validates our given request against the logic we are checking. -// The function takes a request data map, and a public key in the form of a -// string. If the public key is an empty string, the function will run an -// external program to fetch the node's public key from a Tor HSDir. -// -// ValidateReq will first validate "nodetype", looking whether the announcer -// is a node or a directory. -// Then, it will validate the onion address using a regular expression. -// Now, if pubkey is empty, it will run the external program to fetch it. If a -// descriptor can't be retrieved, it will retry for 10 times, and fail if those -// are not successful. -// -// Continuing, ValidateReq will verify the RSA signature posted by the -// announcer. -// If any of the above are invalid, the function will return nil and false. -// Otherwise, it will return the pubkey as a slice of bytes, and true. -func ValidateReq(req map[string]string, pubkey string) ([]byte, bool) { - // Validate nodetype. - if req["nodetype"] != "node" { - return nil, false - } - // Validate address. - re, err := regexp.Compile("^[a-z2-7]{16}\\.onion$") - CheckError(err) - if len(re.FindString(req["address"])) != 22 { - return nil, false - } - log.Println(req["address"], "seems valid") - - if len(pubkey) == 0 { - // Address is valid, we try to fetch its pubkey from a HSDir - cnt := 0 - for { // We try until we have it. - cnt++ - if cnt > 10 { - // We probably can't get a good HSDir. The client shall retry - // later on. - return []byte("Couldn't get a descriptor. Try later."), false - } - pubkey = FetchHSPubkey(req["address"]) - if strings.HasPrefix(pubkey, "-----BEGIN RSA PUBLIC KEY-----") && - strings.HasSuffix(pubkey, "-----END RSA PUBLIC KEY-----") { - log.Println("Got descriptor!") - break - } - time.Sleep(2000 * time.Millisecond) - } - } - // Validate signature. - msg := []byte(req["message"]) - sig := []byte(req["signature"]) - pub, err := ParsePubkeyRsa([]byte(pubkey)) - CheckError(err) - - val, err := VerifyMsgRsa(msg, sig, pub) - CheckError(err) - if val != true { - return nil, false - } - - return []byte(pubkey), true -} - -// HTTPPost sends an HTTP POST request to the given host. -// Takes the host to request and the data to post as arguments. -// If the host ends with ".onion", it will enable the request to be performed -// over a SOCKS proxy, defined in ProxyAddr. -// On success, it will return the http.Response. Otherwise, it returns an error. -func HTTPPost(host string, data []byte) (*http.Response, error) { - socksify := false - parsedHost, err := url.Parse(host) - if err != nil { - return nil, err - } - hostname := parsedHost.Hostname() - if strings.HasSuffix(hostname, ".onion") { - socksify = true - } - httpTransp := &http.Transport{} - httpClient := &http.Client{Transport: httpTransp} - if socksify { - log.Println("Detected a .onion request. Using SOCKS proxy.") - dialer, err := proxy.SOCKS5("tcp", ProxyAddr, nil, proxy.Direct) - if err != nil { - return nil, err - } - httpTransp.Dial = dialer.Dial - } - request, err := http.NewRequest("POST", host, bytes.NewBuffer(data)) - if err != nil { - return nil, err - } - request.Header.Set("Content-Type", "application/json") - - resp, err := httpClient.Do(request) - if err != nil { - return nil, err - } - - return resp, nil -} - -// GenRandomASCII returns a random ASCII string of a given length. -func GenRandomASCII(length int) (string, error) { - var res string - for { - if len(res) >= length { - return res, nil - } - num, err := rand.Int(rand.Reader, big.NewInt(int64(127))) - if err != nil { - return "", err - } - n := num.Int64() - if n > 32 && n < 127 { - res += string(n) - } - } -}