sign签名(from表单格式)

本文详细介绍了在接口开发中如何保障安全性,重点讲解了使用sign签名进行安全验证的方法。文章解释了sign签名的生成过程,包括参数排序、连接、加密等步骤,并提供了代码示例。同时,文中还涉及了sign签名的验证流程,包括参数检查、时间戳校验等关键环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

做接口开发首先需要注意的是接口的安全性,接口的安全性可以用Https来做或者直接在服务器层面设置ip白名单,我用的是sign签名来确保接口的安全性。
sign签名是根据用户请求的参数和值(不包括sign),结合分配给客户端的秘钥(securityKey),通过算法生成的签名。在算签名时,首先要按照每组的key=value的字母顺序做排序(升序降序自己拿主意),然后对key和vaule(使用utf-8编码,特别注意包含中文的参数值的编码)连接成没有等于号的字符串;然后在最后加上客户端的秘钥(securityKey)然后通过算法生成签名。
在此需要注意,前端必传的字段有:
appkey(String类型,必填,调用者第三方公钥)
timeStamp(String类型,必填,当前时间戳 格式(自己拿主意,只要前后端一致就行))
sign(String类型,必填,(参数+必传参数+私钥(securityKey,))的算法加密串)
假如参数有:
String appkey=“abcd”
String timeStamp=“20190422125727”
String aaa=“aaa”
String bbb=“bbb”
该客户端的秘钥是securityKey=1234567890
按照key=value升序排序是
aaa=aaaappkey=abcdbbb=bbbtimeStamp=20190422125727
连接成没有等于号的字符串
aaaaaaappkeyabcdbbbbbbtimeStamp20190422125727
拼接客户端秘钥
aaaaaaappkeyabcdbbbbbbtimeStamp201904221257271234567890
然后使用加密算法加密
sign=(aaaaaaappkeyabcdbbbbbbtimeStamp201904221257271234567890)加密后的串
在贴代码之前需要和大家说的问题是,post请求获取参数是可能是form表单格式或者是json格式,不同的格式获取参数的方式也是不同的,这一片博文先用post请求的form表单格式写,下一篇博文再介绍json格式。

/**
     * @param httpServletRequest
     * @param httpServletResponse
     * @return void
     * @Description: 验证sign签名
     * @Author Zangdy
     * @CreateTime 2019/4/22 12:57
     */
    private boolean verifySign(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        // 签名
        String appKey = httpServletRequest.getParameter("appKey");
        String timeStamp = httpServletRequest.getParameter("timeStamp");
        String sign = httpServletRequest.getParameter("sign");
        // 判断参数是否为空
        if (StringUtils.isBlank(appKey) || StringUtils.isBlank(timeStamp) || StringUtils.isBlank(sign)) {
            DyTool.responseWriter(StatusEnum.APPTIMESIGNISNULL, httpServletResponse);  // (工具类)
            return false;
        } else {
            StringBuilder signSb = new StringBuilder();
            // 获取url里的请求参数
            Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
            for (String paraName : parameterMap.keySet()) {
                if (!"sign".equals(paraName)) {   // 拼接参数
                    signSb.append(paraName).append("=").append(parameterMap.get(paraName)[0]).append("&");
                }
            }
            String signNew = signSb.substring(0, signSb.length() - 1); // 获取拼接后的参数
            String[] signArr = signNew.split("&");
            Arrays.sort(signArr);  // 将参数排序
            signNew = DyTool.arrToString(signArr);  // (工具类)数组转字符串
            signNew = signNew.replace("=", "");
            signNew += securityKeyLocal;  // 拼接securityKey
            if (!sign.equals(SecuritySHATool.shaEncrypt(signNew))) {
                DyTool.responseWriter(StatusEnum.SIGNUNTRUE, httpServletResponse);  // (工具类)
                return false;
            }
            long time = new Date().getTime();  // 获取当前时间毫秒值
            try {
            //DyTool.timeStampToDateTime  工具类,将yyyyMMddHHmmss转成毫秒值
                if ((time - DyTool.timeStampToDateTime(timeStamp)) > (1000 * 60 * 5)) { // 一次请求签名在五分钟内
                    DyTool.responseWriter(StatusEnum.SIGNOUTTIME, httpServletResponse); // (工具类)
                    return false;
                }
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return true;
    } 
        /**
     * @param timeStamp  yyyyMMddHHmmss格式时间戳
     * @return java.lang.Long
     * @Description: 将时间戳转为毫秒值   yyyyMMddHHmmss
     * @Author Zangdy
     * @CreateTime 2019/4/22 11:42
     */
    public static Long timeStampToDateTime(String timeStamp) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.parse(timeStamp).getTime();
    }
    /**
     * @param statusEnum  状态枚举类
     * @param response
     * @return java.lang.String
     * @Description: 发生错误时响应给请求者, 拦截器过滤器层面,需要HttpServletResponse
     * @Author Zangdy
     * @CreateTime 2019/4/22 11:09
     */
    public static void responseWriter(StatusEnum statusEnum, HttpServletResponse response) throws IOException {
        ResponseBody responseBody = new ResponseBody();
        responseBody.setStatus(statusEnum.getStatus());
        responseBody.setMessage(statusEnum.getMessage());
        response.setContentType("text/html; charset=UTF-8");
        response.getWriter().write(JSONObject.toJSONString(responseBody));
    }
/**
 * @Description:  状态枚举类
 * @Author: 臧东运
 * @CreateTime: 2019/4/22 10:53
 */
public enum  StatusEnum {

    UNLOGIN("0000","未登录"),

    /**
     * 状态 02 开头为成功
     * */
    LOGINSUCCESS("0201","登陆成功"),
    LOGOUTSUCCESS("0202","注销成功"),
    UPLOADSUCCESS("0203","文件上传成功"),


    /**
     * 状态 04 开头为失败
     */
    IPBLOCKED("0400","因非法请求,已被禁止访问,请稍后重试!"),
    ACCESSDENIED("0401","无权限访问"),
    USERNAMEISNULL("0402","用户名不能为空!"),
    PASSWORDISNULL("0402","密码不能为空!"),
    USERNAMEERROR("0402","用户名错误!"),
    PASSWORDERROR("0402","密码错误!"),
    NOHEADER("0403","缺少头信息"),
    TOKENOUTTIME("0404","token认证已过期"),
    APPTIMESIGNISNULL("0405","sign认证参数为空"),
    SIGNUNTRUE("0406","sign参数不正确"),
    SIGNOUTTIME("0407","sign签名已过期"),

    ERROR("0499","服务器异常,请稍后重试。如有问题,请联系管理员"),
    CUSTOM("0500","自定义异常");

    private String status;
    private String message;

    StatusEnum(String status, String message) {
        this.status = status;
        this.message = message;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

/**
 * @Description:  加密算法工具类
 * @Author: 臧东运
 * @CreateTime: 2019/4/22 14:10
 */
public class SecuritySHATool {
    public static final String KEY_SHA = "SHA";
    public static final String KEY_MD5 = "MD5";


    /**
     * MD5加密字节
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptMD5(byte[] data) throws Exception {
        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
        md5.update(data);
        return md5.digest();

    }

    /**
     * SHA加密字节
     *
     * @param data
     * @return
     * @throws Exception
     */
    public static byte[] encryptSHA(byte[] data) throws Exception {
        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
        sha.update(data);
        return sha.digest();
    }


    /**
     * SHA加密
     *
     * @param inputStr
     * @return
     */
    public static String shaEncrypt(String inputStr) {
        byte[] inputData = inputStr.getBytes();
        String returnString = "";
        try {
            inputData = encryptSHA(inputData);
            for (int i = 0; i < inputData.length; i++) {
                returnString += byteToHexString(inputData[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return returnString;
    }


    private static String byteToHexString(byte ib) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a',
                'b', 'c', 'd', 'e', 'f'};
        char[] ob = new char[2];
        ob[0] = Digit[(ib >>> 4) & 0X0F];
        ob[1] = Digit[ib & 0X0F];
        String s = new String(ob);
        return s;
    }

    /**
     * MD5加密
     *
     * @param inputStr
     * @return
     */
    public static String md5Encrypt(String inputStr) {
        byte[] inputData = inputStr.getBytes();
        String returnString = "";
        try {
            BigInteger md5 = new BigInteger(encryptMD5(inputData));
            returnString = md5.toString(16);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return returnString;
    }

    public static String dataDecrypt(Map<String, Object> serviceParams) {
        StringBuilder sb = new StringBuilder();
        Object[] keys = serviceParams.keySet().toArray();
        Arrays.sort(keys);
        for (Object key : keys) {
            sb.append(key).append(serviceParams.get(key));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(shaEncrypt("username111password111"));
        System.out.println(md5Encrypt("111"));
    }

如果发现什么问题请留言,毕竟代码都是人写的难免会出错。

<think>我们正在开发一个提供苹果证书在线签名服务的网站。苹果应用签名通常涉及企业证书或个人证书,用于对未通过App Store分发的应用进行签名。用户上传未签名的IPA文件,服务端使用苹果证书(如企业开发者证书)进行签名,然后用户可下载签名后的IPA文件进行安装。 开发这样一个网站需要解决的关键问题: 1. **证书管理**:安全地存储和管理苹果开发者证书(.p12文件)和对应的描述文件(.mobileprovision)。 2. **签名服务**:在服务器端执行签名操作,通常使用命令行工具`codesign`或自动化脚本(如`zsign`或`isign`)进行重签名。 3. **用户界面**:提供用户上传IPA文件、选择签名证书(如果支持多证书)、执行签名和下载签名后文件的功能。 4. **安全考虑**:由于涉及苹果开发者证书,必须确保服务的安全性,防止证书泄露和滥用。 5. **合规性**:苹果对签名行为有严格规定,需确保服务不违反苹果的开发者协议。 ## 开发步骤 ### 1. 后端开发(签名服务) 签名服务是核心功能。我们可以使用现有的开源签名工具,如`zsign`(纯C++实现,支持多平台)或`isign`(Python)。这里以`zsign`为例。 #### 安装依赖 在Ubuntu服务器上,安装`zsign`所需依赖: ```bash sudo apt update sudo apt install -y build-essential cmake zlib1g-dev ``` #### 编译zsign ```bash git clone https://github.com/zhlynn/zsign.git cd zsign mkdir build cd build cmake .. make ``` #### 签名命令示例 ```bash ./zsign -k certificate.p12 -p 'p12_password' -m provision.mobileprovision -o output.ipa input.ipa ``` ### 2. 后端服务框架 使用Python Flask框架搭建一个简单的Web服务,处理文件上传和签名请求。 #### 安装Flask ```bash pip install flask ``` #### 示例代码(app.py) ```python import os import subprocess from flask import Flask, request, send_file, jsonify app = Flask(__name__) UPLOAD_FOLDER = 'uploads' SIGNED_FOLDER = 'signed' ALLOWED_EXTENSIONS = {'ipa'} app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['SIGNED_FOLDER'] = SIGNED_FOLDER def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/sign', methods=['POST']) def sign_ipa(): if 'file' not in request.files: return jsonify({'error': 'No file part'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': 'No selected file'}), 400 if file and allowed_file(file.filename): # 保存上传的IPA文件 upload_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) file.save(upload_path) # 生成签名后的IPA路径 signed_filename = "signed_" + file.filename signed_path = os.path.join(app.config['SIGNED_FOLDER'], signed_filename) # 使用zsign进行签名(这里假设证书和描述文件已放在服务器上) # 实际应用中,证书和描述文件可能需要从数据库或配置中获取 cert_path = "certs/certificate.p12" cert_password = "your_password" provision_path = "certs/provision.mobileprovision" # 调用zsign cmd = [ './zsign', '-k', cert_path, '-p', cert_password, '-m', provision_path, '-o', signed_path, upload_path ] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: # 签名失败 return jsonify({'error': result.stderr}), 500 # 返回签名后的文件 return send_file(signed_path, as_attachment=True) else: return jsonify({'error': 'Invalid file type'}), 400 if __name__ == '__main__': os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(SIGNED_FOLDER, exist_ok=True) app.run(ssl_context='adhoc') # 使用adhoc生成自签名证书用于测试 ``` ### 3. 前端开发 使用HTML+JavaScript实现文件上传表单,并显示上传进度。 #### 示例HTML(index.html) ```html <!DOCTYPE html> <html> <head> <title>IPA在线签名</title> </head> <body> <h1>上传IPA文件进行签名</h1> <form id="uploadForm"> <input type="file" name="file" id="fileInput" accept=".ipa"> <button type="submit">签名</button> </form> <div id="progress"></div> <script> document.getElementById('uploadForm').addEventListener('submit', function(e) { e.preventDefault(); var fileInput = document.getElementById('fileInput'); var file = fileInput.files[0]; if (!file) { alert('请选择文件'); return; } var formData = new FormData(); formData.append('file', file); var xhr = new XMLHttpRequest(); xhr.open('POST', '/sign', true); // 显示上传进度 xhr.upload.onprogress = function(e) { if (e.lengthComputable) { var percent = (e.loaded / e.total) * 100; document.getElementById('progress').innerHTML = '上传进度: ' + percent.toFixed(2) + '%'; } }; xhr.onload = function() { if (xhr.status === 200) { // 创建下载链接 var blob = new Blob([xhr.response], {type: 'application/octet-stream'}); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = 'signed_' + file.name; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } else { alert('签名失败: ' + xhr.responseText); } }; xhr.responseType = 'blob'; xhr.send(formData); }); </script> </body> </html> ``` ### 4. 安全与优化 - **证书安全**:将证书存储在安全的存储系统中(如Vault),而不是直接放在服务器文件系统中。 - **访问控制**:对签名服务进行身份验证,确保只有授权用户可以使用。 - **错误处理**:完善错误日志,避免敏感信息泄露。 - **性能优化**:签名过程可能耗时,考虑使用任务队列(如Celery)异步处理,避免HTTP请求超时。 - **多证书支持**:允许用户选择不同的证书签名(需前端配合)。 ### 5. 注意事项 - 苹果企业开发者账号每年需要续费,且签名应用的分发需遵守苹果政策,避免滥用导致证书被封。 - 签名后的应用安装需要用户信任企业证书(在设置->通用->设备管理中信任)。 ## 相关问题 1. 如何防止苹果证书被滥用导致被封? 2. 除了`zsign`,还有哪些常用的IPA签名工具? 3. 如何实现签名任务的异步处理? 4. 如何安全地存储苹果开发者证书和描述文件? 5. 如何支持多个苹果开发者证书进行签名?</think>要开发一个支持苹果证书在线签名的网站,需要解决证书管理、签名流程、安全防护等核心问题。以下是关键开发步骤和技术实现方案: ### 一、核心技术架构 1. **签名引擎** - 使用开源工具实现签名核心功能: ```bash # 示例:使用zsign工具签名 zsign -k certificate.p12 -m provision.mobileprovision -p 密码 -o signed.ipa original.ipa ``` - 推荐工具:`zsign`(纯C++实现)或`isign`(Python库)[^1] 2. **证书管理系统** - 安全存储证书(.p12 + .mobileprovision) - 实现证书自动检测机制: ```python def check_cert_validity(p12_file): # 使用OpenSSL验证证书有效期 result = subprocess.run(['openssl', 'pkcs12', '-in', p12_file, '-nokeys'], capture_output=True) return "Not After" in result.stdout.decode() ``` ### 二、核心开发流程 1. **用户交互层** ```html <!-- 前端上传组件示例 --> <form id="sign-form"> <input type="file" accept=".ipa" required> <select name="certificate"> <option value="cert1">企业证书A</option> </select> <button type="submit">开始签名</button> </form> <div id="progress-bar"></div> ``` 2. **签名服务端(Python Flask示例)** ```python from flask import Flask, request, send_file import subprocess app = Flask(__name__) @app.route('/sign', methods=['POST']) def sign(): ipa_file = request.files['ipa'] cert_id = request.form['certificate'] # 获取证书路径 cert = get_cert_from_db(cert_id) # 执行签名命令 cmd = f'zsign -k {cert.p12_path} -m {cert.provision_path} -p {cert.password} -o signed.ipa {ipa_file.path}' subprocess.run(cmd, shell=True, check=True) return send_file('signed.ipa', as_attachment=True) ``` 3. **安全防护机制** - 证书隔离:每个用户会话使用独立容器(Docker) - 访问控制:JWT令牌验证 - 流量加密:强制HTTPS(使用Let's Encrypt证书) ### 三、关键注意事项 1. **证书合规性** - 仅使用合法获取的苹果开发者证书($299/年的企业账号) - 限制单证书每日签名次数(建议<100次/日)[^2] 2. **防止滥用措施** - 实现人机验证(reCAPTCHA v3) - 签名队列系统(Celery + Redis) - IP频率限制(Nginx限流模块) 3. **苹果政策风险规避** - 禁止公开分发签名服务 - 添加用户协议声明:"仅限开发者自用测试" - 监控苹果证书吊销列表(OCSP) ### 四、部署方案 ```mermaid graph LR A[用户] --> B(Nginx负载均衡) B --> C[Web服务器] C --> D[签名队列] D --> E[签名集群] E --> F[安全存储] F --> G[CDN分发] ``` ### 五、成本估算 | 项目 | 基础方案 | 备注 | |------------|-------------------|--------------------------| | 服务器 | 4核8G × 3 ($200/月) | 高IO需求 | | 证书 | $299/年 | 苹果企业开发者账号 | | 存储 | S3兼容存储 ($0.023/GB) | 需加密存储 | | 安全审计 | $3000/次 | 每年至少1次 | > **法律风险提示**:该服务可能违反[《Apple Developer Program License Agreement》第3.2(f)条](https://developer.apple.com/terms/),建议提供签名服务前咨询法律意见[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值