加密通信软件Signal-Server编译安装折腾手记(Ubuntu 20.04.2 LTS)-20240613更新
加密通信软件Signal是开源的,安全性很高,号称斯诺登也推荐大家使用。
编译
言归正传,继续说Signal服务端。首先,Signal Server目前的代码是2.92版本,既然有工程文件pom.xml,那就用Maven编译好了。仓库地址是https://github.com/signalapp/Signal-Server/。
mvn install -DskipTests
编译这个版本的代码需要用JDK 11+。编译的最终产物是TextSecureServer-3.21.jar。
寻找别人的安装经验指南
编译好了,接下来要部署了。先谷歌一下关键字Signal Sever + Install/Deploy,引用最多的是一篇1.88版本的安装指南:《Signal Server Installation Guide》。
配置的总入口:模板文件sample.yml
把Signal Server代码仓库中的配置模板文件sample.yaml拿出来,改名为config.yml,按上面这个指南配置,会发现3.21 的配置项有了一些变化。后面的事实会证明,代码仓库中的这个yml模板,是很老的版本,和代码不完全匹配,照这个配几乎无用。
第三方接口的帐号申请
先搜罗一下yml中需要申请的第三方接口的帐号,挨个申请。
要申请的帐号清单:
- Twilio 用于短信等功能。
- S3 亚马逊S3,用于图床CDN、聊天的附件等,自己部署可以用minIO替代。
- SQS 亚马逊SQS,消息队列。
- reCAPTCHA 谷歌reCAPTCHA,用于识别机器人。
- GCM 谷歌推送服务。国内可用极光推送。
- APN 苹果推送服务。
- 谷歌对象存储
- bigtable 谷歌大数据数据库
配置服务器的域名
没有域名的话,买一个。HTTPS/WSS(WebSocket Secure)服务都是需要校验域名对应的证书的,Twilio服务的反向连接也需要配置域名。
安装Redis(最新为redis集群)
sudo apt-get install -y redis-server
修改配置文件:
sudo vi /etc/redis/redis.conf
重启Redis:
sudo systemctl restart redis.service
安装PostgreSQL(新版本已改为dynamodb,不再使用pg)
安装数据库软件
安装coturn
基于webrtc协议的1v1实时语音聊天
可参考文章 https://docs.wildfirechat.cn/webrtc/turn_server.html
开启防火墙tcp和udp的3478端口,udp也要开。
安装
apt-get install libevent-dev apt install certbot python3-certbot-nginx
下载安装github上的coturn
https://github.com/coturn/coturn
安装coturn并生成证书:
openssl req -x509 -newkey rsa:2048 -keyout /etc/turn_server_pkey.pem -out /etc/turn_server_cert.pem -days 99999 -nodes
编辑配置文件:
sudo vi /etc/turnserver.conf
先查找pem文件,然后配置下面的结果附加到文件末尾dh-file字段:
$ sudo find / -name ssl-dhparams.pem /usr/lib/python3/dist-packages/certbot/ssl-dhparams.pem
listening-ip= 内网ip external-ip= 外网ip
#放开no-auth测试
no-auth realm=服务器的完整域名 cert=/etc/turn_server_cert.pem pkey=/etc/turn_server_pkey.pem
#放开管理密码 cli-password=
dh-file=/usr/lib/python3/dist-packages/certbot/ssl-dhparams.pem
配置好之后,启动
turnserver -f -c /etc/turnserver.conf
然后用下面的页面测试coturn的基本功能(用谷歌浏览器可能报701但不影响,建议火狐浏览器):
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
界面中有relay就认为成功
注意:coturn有两套认证机制,一套是用户名、密码的,一套是共享密钥(secret)。其中共享密钥是给REST接口使用的,Signal Server要用到共享密钥。而测试coturn基本功能时使用的页面只支持用户名+密码的认证方式,而且coturn是优先使用共享密钥的,也就是说如果同时配了共享密钥、用户名+密码的话,在上面这个测试页面中是测试不过的。所以要先禁用共享密钥的设置,等在上面的这个页面中测试通过后,再把共享密钥的配置恢复。
配置好共享密钥后,启动daemon:
turnserver -f -o -c /etc/turnserver.conf
安装Apache2(可nginx替代)
在防火墙上对外开放服务器的80端口,因为获取免费证书时需要回连80端口来验证。
下面用a2enmod打开需要的几种Apache代理模块,用certbot获取域名对应的免费HTTPS证书,最后会提示将80端口重定向到443端口。
sudo apt-get install apache2 sudo a2enmod proxy proxy_http proxy_wstunnel sudo add-apt-repository ppa:certbot/certbot -y sudo apt-get update -y sudo apt-get install python-certbot-apache -y sudo certbot --authenticator standalone --installer apache -d "你的服务器完整域名" --pre-hook "systemctl stop apache2" --post-hook "systemctl start apache2"
编辑Apache的HTTPS站点的配置文件:
sudo vi /etc/apache2/sites-available/000-default-le-ssl.conf
设置反向代理,将Apache的443端口来的客户端请求反向代理到Signal Server的8080端口。443的wss://反向代理到8080的ws://,443的https://反向代理到8080的http://。Signal Server除了用8080端口来监听ws://和http://,还会起一个8081的对内管理端口,可以用浏览器打开http://127.0.0.1:8081查看。
HTTPS站点的配置样本如下:
<IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin webmaster@excmple.com DocumentRoot /var/www/html AllowEncodedSlashes NoDecode RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule .* "ws://localhost:8080$0" [P,L] ProxyRequests off ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ #LogLevel trace6 ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined ServerName 你的服务器完整域名 SSLCertificateFile /etc/letsencrypt/live/你的服务器完整域名/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/你的服务器完整域名/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost> </IfModule>
其中HTTPS证书是Signal客户端也需要用到的。
上述反向代理的规则是通过谷歌抄自"How to Reverse Proxy Websockets with Apache 2.4"。这个配置首先用RewriteEngine匹配客户端来的wss://协议中的协议提升字段,匹配上的就转发到ws://,没匹配的用ProxyPass反向代理到http://。
安装CDS
Signal的CDS(联系人发现服务,手机通讯录联系人查询功能,为了安全性才比较复杂)。CDS一般并不和Signal Server部署在一台机器上,而是使用独立的特殊硬件来保证其安全性,也就是Intel SGX。SGX能创建硬件隔离区域,称为enclave。目前云服务提供商中只有微软Azure和Intel合作提供了云上的SGX的试用环境,而且有地理区域限制。
有兴趣的可以参考折腾过CDS的人写的指南:https://github.com/on-premise-signal/signal-setup-guide。
CDS的代码仓库是:https://github.com/signalapp/ContactDiscoveryService。
最新版:https://github.com/signalapp/ContactDiscoveryService-iceLake
cds要求安装/intel/linux-sgx 等驱动 https://github.com/intel/linux-sgx/tree/sgx_2.1.3#install-the-intelr-sgx-psw
Signal Server的处女Run
初始化数据库,建表(此处暂未深究为什没有处理名为readdb的数据库):
java -jar TextSecureServer-3.21.jar messagedb migrate config/config.yml java -jar TextSecureServer-3.21.jar accountdb migrate config/config.yml java -jar TextSecureServer-3.21.jar abusedb migrate config/config.yml
填满config.yml的各个字段后,尝试运行Signal Server:
java -jar target/TextSecureServer-3.21.jar server config/config.yml
结果不出意料:是跑不起来的。好在打印的错误信息明确指明了是哪个配置项有问题,只能去抠代码了(因为Signal的开发人员都说了,有代码不就有了一切嘛),从此要开始“看错误信息 -> 谷歌搜错误/看代码/调代码 -> 改配置”的循环
新的配置模板文件config.yml
好在Signal Server的配置项解析代码是集中在service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerConfiguration.java这个文件中的。做一个新的config.yml模板:
twilio: accountId: accountToken: numbers: # array localDomain: messagingServicesId: push: queueSize: 200 attachments: accessKey: accessSecret: bucket: region: cdn: accessKey: accessSecret: bucket: region: cache: url: replicaUrls: # array directory: redis: url: replicaUrls: # array sqs: accessKey: accessSecret: queueUrl: region: us-east-1 client: userAuthenticationTokenSharedSecret: # hex string userAuthenticationTokenUserIdSecret: # hex string server: replicationUrl: replicationPassword: replicationCaCertificate: accountDatabaseCrawler: chunkSize: 1000 chunkIntervalMs: 8000 pushScheduler: url: replicaUrls: # array messageCache: redis: url: replicaUrls: # array persistDelayMinutes: 10 messageStore: driverClass: org.postgresql.Driver user: password: url: # example: jdbc:postgresql://127.0.0.1:5432/messagedb abuseDatabase: driverClass: org.postgresql.Driver user: password: url: # example: jdbc:postgresql://127.0.0.1:5432/abusedb testDevices: # array: [ { "number" => "", "code" => "" }, { "number" => "", "code" => "" }, ... ] maxDevices: # array: [ { "number" => "", "count" => "" }, { "number" => "", "count" => "" }, ... ] keysDatabase: driverClass: org.postgresql.Driver user: password: url: # example: jdbc:postgresql://127.0.0.1:5432/keysdb accountsDatabase: driverClass: org.postgresql.Driver user: password: url: # example: jdbc:postgresql://127.0.0.1:5432/accountdb read_database: driverClass: org.postgresql.Driver user: password: url: # example: jdbc:postgresql://127.0.0.1:5432/readdb #limits: #use default values in source code #httpClient: #use default values in source code #webSocket: #use default values in source code turn: secret: uris: # array gcm: senderId: apiKey: apn: pushCertificate: pushKey: bundleId: sandbox: false unidentifiedDelivery: certificate: privateKey: expiresDays: voiceVerification: url: locales: # array recaptcha: secret: storageService: userAuthenticationTokenSharedSecret: # hex string backupService: userAuthenticationTokenSharedSecret: # hex string transparentDataIndex: # Map<String, String> #server: #use default values in source code #logging: #use default values in source code #metrics: #use default values in source code
重新把这个config.yml的各个字段填满,别高兴太早,依然是跑不起来的,因为有些字段有严格的要求,是在代码中通过Java标注来要求的。
比如testDevices这个配置项,在WhisperServerConfiguration.java中是这么写的:
@Valid @NotNull @JsonProperty private List<TestDeviceConfiguration> testDevices = new LinkedList<>();
说明testDevices是json格式,代码直接给new了一个空集作为默认值,可以不配置,但是如果你在yml文件中配置了这项的话,一定要有值,也就是要给它配上至少一个{number, code}对。所以干脆在yml中注释掉它好了。maxDevices等配置项也类似。
再比如storageService和backupService下面的userAuthenticationTokenSharedSecret这个字段,解析代码如下。显然这是一个十六进制字符串格式的字段,而且不能为空串。
@NotEmpty @JsonProperty private String userAuthenticationTokenSharedSecret; public byte[] getUserAuthenticationTokenSharedSecret() throws DecoderException { return Hex.decodeHex(userAuthenticationTokenSharedSecret.toCharArray());
配置APN证书-ios推送
国内使用改造为直接对接极光等三方推送
配置Sealed Sender的密钥
更麻烦一点的是unidentifiedDelivery这个功能的配置。这个功能又称为Sealed Sender,即匿名发聊天信息。功能的设计参考https://signal.org/blog/sealed-sender/。这个功能需要证书certificate、私钥privateKey,但并不是PEM等标准格式的。仍然翻一下相关的解析代码,最后会发现Signal Server自带了生成这些数据的命令行参数。具体的代码在service/src/main/java/org/whispersystems/textsecuregcm/workers/CertificateCommand.java这文件中,从这代码可以看出,需要两步走。
在最新版本signal中,客户端是请求 /v1/certificate/delivery接口时,会用这个certificate对信息签名。
第一步:生成CA的证书和私钥。这个CA证书(注意不是CA的私钥)在Signal的客户端中也要用到,客户端访问服务器时可以走HTTPS的代理。
java -jar target/TextSecureServer-3.21.jar certificate --ca
第二步:利用CA的私钥,生成服务器的证书。
java -jar target/TextSecureServer-3.21.jar certificate --key XXX --id NNNN
其中XXX是CA的私钥,NNNN是自己决定的一个数字编号。
ubuntu@ip-172-31-33-23:~/Signal-Server/service$ java -jar target/TextSecureServer-3.21.jar certificate --ca Public key : BVttrBH0mkQ5QVh2BX1CMbCyTNdHANg6BVjo+RxDgJgX Private key: 0MFfk96SBom3rYAU7p861yz/gZCbgXjeYAfWDSL6LW8= ubuntu@ip-172-31-33-23:~/Signal-Server/service$ java -jar target/TextSecureServer-3.21.jar certificate --key 0MFfk96SBom3rYAU7p861yz/gZCbgXjeYAfWDSL6LW8= --id 1 Certificate: CiUIARIhBdRco3McNmr8yy1UIrX3fFqPqBv3CxqC0IJObRB7SKgZEkB+vzBUqH1I6O+SKZSqDllFLkj/DA2qYuqXWvF+KSLSKMIq3xVAciHBtpGOLE19fmJHHIukUVSp0MIPK7y4l24A Private key: AIvy5XjaaWiuQgN1omJ/v18DrG9bpBePC5jEei6HWlc=
最终的配置文件config.yml
最后得到的config.yml的内容。用这个配置,仅仅可以跑通手机客户端的初步注册,其他很多功能仍需要调测。Signal客户端也有一些配置要修改。
twilio: accountId: ACf1111111111111111111111111111 accountToken: 3e22222222222222222222222222 numbers: - +1923456789 localDomain: 你的完整域名 messagingServicesId: MG3333333333333333333333333333 push: queueSize: 200 attachments: accessKey: AKIAAAAAABBBBBCCCCCCC accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD bucket: fake region: ap-southeast-1 cdn: accessKey: AKIAAAAAABBBBBCCCCCCC accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD bucket: fake region: ap-southeast-1 cache: url: http://127.0.0.1:6379 replicaUrls: [ "http://127.0.0.1:6379" ] directory: redis: url: http://127.0.0.1:6379 replicaUrls: [ "http://127.0.0.1:6379" ] sqs: accessKey: AKIAAAAAABBBBBCCCCCCC accessSecret: QhABCCCCCCCCCCCCCDDDDDDDDDDDDDDDD queueUrl: https://sqs.ap-southeast-1.amazonaws.com/17093374723/fake region: ap-southeast-1 client: userAuthenticationTokenSharedSecret: 33445566 #fake userAuthenticationTokenUserIdSecret: 22117788 #fake server: replicationUrl: http://127.0.0.1 #fake replicationPassword: fake replicationCaCertificate: | #fake -----BEGIN CERTIFICATE----- MIIGCDCCBPCgAwIBAgIId9kHAiU6ZxIwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV mYm1yVxw4lLirPPyva9YOqX626RzyKQ0mcOiV0vURjALRiAkNklfpgr877wilvdS dynVUqLbErSibtAh -----END CERTIFICATE----- accountDatabaseCrawler: chunkSize: 1000 chunkIntervalMs: 80000000 pushScheduler: url: http://127.0.0.1:6379 replicaUrls: [ "http://127.0.0.1:6379" ] messageCache: redis: url: http://127.0.0.1:6379 replicaUrls: [ "http://127.0.0.1:6379" ] persistDelayMinutes: 10 messageStore: driverClass: org.postgresql.Driver user: signal password: password url: jdbc:postgresql://127.0.0.1:5432/messagedb abuseDatabase: driverClass: org.postgresql.Driver user: signal password: password url: jdbc:postgresql://127.0.0.1:5432/abusedb keysDatabase: driverClass: org.postgresql.Driver user: signal password: password url: jdbc:postgresql://127.0.0.1:5432/keysdb accountsDatabase: driverClass: org.postgresql.Driver user: signal password: password url: jdbc:postgresql://127.0.0.1:5432/accountdb read_database: driverClass: org.postgresql.Driver user: signal password: password url: jdbc:postgresql://127.0.0.1:5432/readdb #limits: #use default values in source code #httpClient: #use default values in source code #webSocket: #use default values in source code #语音通话 turn: secret: turn #turn安装时/etc下面生成的privatekey uris: - turn:127.0.0.1:3478?transport=udp#配置服务器外网ip gcm: senderId: 71722022434122 apiKey: AAAfffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaakkkkkkkkkkkkkkkkeeeeeeeeeeeee apn: bundleId: fake pushCertificate: | -----BEGIN CERTIFICATE----- MIIGCDCCBPCgAwIBAgIId9kHAiU6ZxIwDQYJKoZIhvcNAQELBQAwgZYxCzAJBgNV mYm1yVxw4lLirPPyva9YOqX626RzyKQ0mcOiV0vURjALRiAkNklfpgr877wilvdS dynVUqLbErSibtAh -----END CERTIFICATE----- pushKey: | -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAsH9uXZaGp2DZ0sfsSI9ovrv4hI2OjEDbzefv5+ZWy0kVdL/r 2UtkH7KXehd9wueaIaZ5+j9PKy3t6x4GPZE4b/fkuXToCijOsbCfhA== -----END RSA PRIVATE KEY----- unidentifiedDelivery: certificate: | CiYI5A8SIQUO+IlR4c9wwDNmYkD4RRJAob3q9UG4vwkaV1k9iiBPhAXv5liN85Q8vFwEJawOWxIng6WwXo/hoXNFb/TinsRPlI5XuSfQs23FDUVRVXZggQ== #fake privateKey: | iCTXE4i2P1t5Y7Vy7SujSQmFfW1E4= #fake expiresDays: 365 voiceVerification: url: http://127.0.0.1 #fake locales: [ "en-US" ] #fake recaptcha: secret: 6LfFFFFFFAAAAAAKKKKKKEEEEEEgJ7kX-pSL storageService: userAuthenticationTokenSharedSecret: 112233445566 #fake backupService: userAuthenticationTokenSharedSecret: 778899AABBCC #fake transparentDataIndex: # Map<String, String> #server: #use default values in source code #logging: #use default values in source code #metrics: #use default values in source code
服务端和客户端代码版本不匹配的问题
最后还有一个坑需要说明:目前(2020-03-16)github上,Signal Server的master分支最新代码和iOS客户端的master分支最新代码并不是匹配的。证据呢?依然是通过日志、调试器这两大利器。
iOS手机端在保存用户的姓名时,会给服务器发一个这样的PUT请求,服务器返回HTTP 400,
/v1/profile/name/MN329VW0j6XOVNH8pgqLOyjmYTdddBp8cBE5B2mPILy4jzAb8PTABCcfep4FfQwPJIRJKQqnhooZjuQrzRSeCeC%2FUzWRqDMN9XFEDIvoCFWrl
Apache的详细log中会显示是Signal Server返回的HTTP 400。Signal Server用的容器框架是Ketty,指定的logger类是SLF4J,而SLF4J的日志开关配置要看它绑定的具体实现,如果要打开日志开关看日志,又要折腾半天,还是用调试器吧。操起IDEA远程调试,在服务器上放开5005调试端口,以调试监听方式启动Signal Server:
java -Xdebug -Xrunjdwp:transport=dt_socket,address=0.0.0.0:5005,server=y,suspend=y -jar TextSecureServer-2.92.jar server config/config.yml
会发现,/v1/profile/name/这个URI需要的参数在base64解码后的长度必须是72,否则Ketty直接返回400,根本不会调用setProfile()函数。
@Timed @PUT @Produces(MediaType.APPLICATION_JSON) @Path("/name/{name}") public void setProfile(@Auth Account account, @PathParam("name") @UnwrapValidatedValue(true) @Length(min = 72,max= 72) Optional<String> name) { account.setProfileName(name.orElse(null)); accountsManager.update(account); }
但是,iOS客户端的代码显示编码后的长度是108,也就是解码后的长度是81,报错长度不匹配。需要用较早一点的iOS客户端代码版本才行。
生成zkParams
java -jar target/TextSecureServer-3.21.jar zkparams
填入config.yml最下面的
zkConfig: serverSecret: JyH42NTp10znCdBYdvQw9wGuXTk0zjh1PxwxFPlbOAjp4MYv7Lw5FkCNJyidCjknLr1QPB9MD4SHn1t8VtV3DFYAd16B+vQvtHvwdG6N1yw4axE13fOjnK88412ibz5x+JBjg4DlxIkuTQIvYwNqanG3mBNOwxdVsSIUeJeiuggGN3QiKjXY1ON7AOVdlqBVl2oBsayuQXmPxQd02zGVCMnOkeExqF7n2yZjLKcJVStbSLJUYu6JLkUEayEFEKYIrTEbYXmqJ+LGCgj2yTL0XqdrY8RINIWiW6uNVH2azwFM22tTMUppcI9FbeCjeCtZXi68ZiIz923ghAd1m5PzCSZ2UhcYCJvjJzwAOV6YZl7bUlE6BGkloZPfnc+FH4sAoi79doKTj+FAK4geClr3fPZEh8igLt4QxGEpJrn1bwQLgrJWBqwXZlAYZ31w2IaHauPclyNm5cgA/76S1jvtC24cOSMNisn1LK0qLuSH7ucwPj9tYuKUn0gMe7zZf9Fo9h+Zpu03LHhXl34ECGZt/3c8xMlpHwMDuxyt4vTdJR1FT/IDQL6gJT+gVV+TbOzb+q4E3JjmQqpmOvscr8tvCQqz3340pRiW353GPVRJrPgikM0VEgRL1Pzcg5Nc5lMKpplO/sCEylBGltp3740MtacXzDWMi1CkXpFe0ARqrhedb6NLlwAVkxV+cdYJtJA0UGhR0fuI172mZPhtC/VJDLxQQOOdK+ZQ88bIILDZm9zdek8FEtRk2iXTD4tVKvIJRcnq3pn5RdO21pVmWfKtQ1tNT7l5JqAAUomxjK8aIgygFlv58gAtNpeDa3kVSDsHJHiHy01psIHsaHLvM1TQA7xd/3vHpYFOgLHQGXqcYg2wuWQ1Ld0j9uUM+8yFVEQN4LRY4KeKtnpa/v1o4eNA+uIm/CNXcbXVifS71ch+uA5b/1fBpgBsmS0easpeQZB2PLLEJFtvJbL2ln2Uc6ZrBtpvXGCNEEATIbuJscTlBqMrv9CC21A6iPBRQZAf0WkFmviwr3C06gyJ8tiwriYWSTivH5LgJcIL24F3vUf2J1ke5dO6ZIjJxPm9MEl4YQqnjHESFsuv1BXzQRsn9lvwEZZygL/292ktQqaZ7RovScLM9AgZbNxkjeuhD9dbxIEI8BMyEnTr82yZnXE5MSEJy38gVloVHm4L+PLHJZwcXVQ serverPublic: bhw5Iw2KyfUsrSou5Ifu5zA+P21i4pSfSAx7vNl/0Wj2H5mm7TcseFeXfgQIZm3/dzzEyWkfAwO7HK3i9N0lHZr4sK9wtOoMifLYsK4mFkk4rx+S4CXCC9uBd71H9idZHuXTumSIycT5vTBJeGEKp4xxEhbLr9QV80EbJ/Zb8BHwEzISdOvzbJmdcTkxIQnLfyBWWhUebgv48sclnBxdVA enabled: ture
客户端ios为例,需要将这个
serverPublic做base64,配置在
serverPublicParamsBase64="Ymh3NUl3Mkt5ZlVzclNvdTVJZnU1ekErUDIxaTRwU2ZTQXg3dk5sLzBXajJINW1tN1Rjc2VGZVhmZ1FJWm0zL2R6ekV5V2tmQXdPN0hLM2k5TjBsSFpyNHNLOXd0T29NaWZMWXNLNG1Ga2s0cngrUzRDWENDOXVCZDcxSDlpZFpIdVhUdW1TSXljVDV2VEJKZUdFS3A0eHhFaGJMcjlRVjgwRWJKL1piOEJId0V6SVNkT3Z6YkptZGNUa3hJUW5MZnlCV1doVWViZ3Y0OHNjbG5CeGRWQQ=="
由于落后客户端版本
升级zkgoup-java到0.7.0后重新生成
Public: APYNGrlVtwuNGiQSgYK/CduMeLLbPVejvNeS/vATQ9hOfipj8mR94eeS28PCVRXUpSRNMRc0WAjqurHi82XfIX+W4lcDbAljgGucmQYyZ7W5zZsuHSbmH3b77p7nXGP/asSFJoOJXAGi8m2iXhVybpQ0AeyOi36an5E6vSOzzdEy5kMLWKjHdeTJOsCCezA0CAa7eLJA+UXp60liO73wZy4 Private: ACyAUqsm7WRUIWlWPkizJ6xAq0ZjZmlNCnDo28GGifMMCjbOnPZIGsmd2ICN5EqL1BjqO/O9m0otsbLjz8CehwaovbaTOKnbJPaXoTPWkSDH9yPcOnmvj6xdm4DOiB37MVTut6DL9yHVdg7S/XnR2amQ6WoyGRI+MwPCFspVimwHKd4oYHDrKTjCndhZnKx2oQcgXVp05JtJfsIRBrBRNg8PEr6FAcIRWeCG6quLxIqfcuPf3Bdn1AgvQz5II369BZsU5rVKTnwjF6CZmVN8UO++JqaK9kqO9YLP3zpMLB4AEmnezjJ7kHFNQaVwVRm0p9Rt6q42P7UssjavjQXngQzEEMwqHt+K0EJuQcLP6K2OtCw4Ti/fFOZ1sUDFN7DVC/YNGrlVtwuNGiQSgYK/CduMeLLbPVejvNeS/vATQ9hOfipj8mR94eeS28PCVRXUpSRNMRc0WAjqurHi82XfIX+vw1vBwQ3XEK0uHIOtKzCh9QAMrxYJxHIdMjSV5aXIBL4xDBDGUP55J3FtzBn58jEMd8C2cwgfhfJGhcgXxh0A8Ne44mRVHX1jXSdtDQ2b+LSxyGrErI5hPgfImPpF3zqDNt4ddJU8EOv7W5orrnEOb9jCoKdc0lb5SWGthwXTDldR6IeiVdnCn7vQBFei9icAPMQ6O14c/dzZogRNIVEBBLaucQxAeWLjKBYdmleRWhYH6mOOW7nMD7GKBkwS3QAgQyAAqp2pFLsWv2Tn8WdcfieFCMaY/p1DTrP/x3zUCiC3TbjGXd0fi4/kpdW4C6QxS+HjHIEESCGCbnrdlSQKKoYxNSJtu4/NtctgFzxo7anCcfi/o8w2O+yJy5ghdgeW4lcDbAljgGucmQYyZ7W5zZsuHSbmH3b77p7nXGP/asSFJoOJXAGi8m2iXhVybpQ0AeyOi36an5E6vSOzzdEy4MoLOGYEd9irEB1caHrh5kmxUmmYyq4RC6HWu5wjJQvmQwtYqMd15Mk6wIJ7MDQIBrt4skD5RenrSWI7vfBnLg
桌面版
使用的是electron框架,2023年代码,目前成功运行6.13.0版本
安卓版
匹配该服务端,已经成功运行5.11.4版本
signal-server,ios,安卓,桌面端,官方2023年6月版本,已经联调成功