当前位置: 首页 > news >正文

Spring Boot对访问密钥加解密——HMAC-SHA256

HMAC-SHA256 简介

HMAC-SHA256 是一种基于 哈希函数 的消息认证码(Message Authentication Code, MAC),它结合了哈希算法(如 SHA-256)和一个密钥,用于验证消息的完整性和真实性。

HMAC 是 “Hash-based Message Authentication Code” 的缩写,它广泛应用于网络通信中,用于保证消息在传输过程中未被篡改,同时也可以校验消息是否来自可信方。


HMAC-SHA256 的组成

HMAC-SHA256 的运算公式如下:

HMAC(key, message) = SHA256((key ⊕ opad) || SHA256((key ⊕ ipad) || message))
  • key:一个秘密密钥,用于认证消息,只有通信双方知道。

  • message:需要进行签名的消息内容。

  • SHA256:一种安全的哈希函数,用于生成固定长度的摘要值。

  • :按位异或操作(XOR)。

  • opadipad

    • opad:外部填充字节 (0x5c)。
    • ipad:内部填充字节 (0x36)。
流程解释:
  1. 将密钥与 ipad 结合,并将其与消息一起进行第一次哈希。
  2. 将密钥与 opad 结合,并将其与第一次哈希的结果一起进行第二次哈希。
  3. 最终输出 HMAC 值。

HMAC-SHA256 的特点

  1. 基于密钥
    • 与普通的 SHA256 不同,HMAC-SHA256 引入了密钥,只有通信双方知道密钥,保证了消息的认证性。
  2. 抗篡改
    • 如果消息在传输过程中被篡改,HMAC 认证将失败。
  3. 抗重放攻击
    • 通常结合时间戳(timestamp)或随机数(nonce),防止消息被恶意重放。
  4. 性能高效
    • 基于哈希函数的算法速度快,适合高并发场景。
  5. 简单易用
    • 常用于 REST API、签名验证和网络协议中。

HMAC-SHA256 的应用场景

  1. API 鉴权
    • 许多云服务和 API 平台(如 AWS、阿里云等)使用 HMAC-SHA256 来验证 API 请求的真实性和完整性。
  2. 消息完整性校验
    • 用于验证消息在传输过程中未被篡改。
  3. 数据完整性校验
    • 用于验证文件或数据传输的完整性(结合密钥,防止恶意伪造)。
  4. 通信协议
    • 用于加密通信协议(如 TLS)的消息认证。
  5. Token 签名
    • 用于生成和验证基于密钥的 Token,如 JWT(JSON Web Token)的签名部分。

HMAC-SHA256 的优点

  1. 安全性强
    • 基于 SHA-256 的安全性,结合密钥使用,安全性更高。
  2. 对抗攻击
    • 有效抵抗暴力破解、哈希碰撞和中间人攻击。
  3. 跨平台支持
    • 各种编程语言和库都支持 HMAC-SHA256。
  4. 性能优越
    • 计算量小,适合高性能需求的场景。

HMAC 与其他加密算法的对比

特性HMAC-SHA256RSA 签名普通哈希
是否需要密钥
加密/认证用途认证认证和加密数据完整性校验
性能高效较慢高效
应用场景API 鉴权、消息验证数字签名、PKI文件校验、数据校验

常见问题

  1. HMAC-SHA256 与普通 SHA-256 有什么区别?
    • SHA-256 是一种单向哈希算法,用于生成固定长度的摘要值;
    • HMAC-SHA256 是基于 SHA-256 和密钥的消息认证码,除了生成摘要外,还能验证消息来源和完整性。
  2. HMAC-SHA256 的密钥如何管理?
    • 密钥必须严格保密,通常存储在安全的配置文件或密钥管理服务(如 AWS KMS)中。
  3. 是否需要 HTTPS?
    • HMAC-SHA256 可以防止消息篡改,但不能保护消息的机密性。建议配合 HTTPS 使用。
  4. 可以用 HMAC-SHA256 替代 RSA 签名吗?
    • 如果双方共享密钥,HMAC-SHA256 是更高效的选择;
    • 如果需要非对称签名或验证,应该使用 RSA。

代码示例

客户端示例

假设这是一个Java客户端(可能是后端服务或桌面应用等),要调用你的服务接口 /api/secure,并用 HMAC-SHA256 做签名。

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;public class HmacClientExample {public static void main(String[] args) throws Exception {// 1) 准备必要参数String accessKeyId = "myKeyId";String accessKeySecret = "myKeySecret"; // 保密String method = "POST";String path = "/api/secure";// 例如携带一个 timestamp (yyyyMMddHHmmss)String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));// 2) 如果有请求体,需要计算 bodyHash (这里只是示例)//   实际可对 JSON 字符串做 MD5 或 SHA256,再 Hex 或 Base64String requestBody = "{\"foo\":\"bar\"}"; // JSONString bodyHash = sha256Hex(requestBody);// 3) 拼装 StringToSign (示例逻辑,可自定义)//    这里用换行分隔 method, path, timestamp, bodyHashString stringToSign = method + "\n" + path + "\n" + timestamp + "\n" + bodyHash;// 4) 做 HMAC-SHA256String signature = hmacSha256Base64(stringToSign, accessKeySecret);// 5) 将签名和 keyId、timestamp 放到 HTTP 头部//    伪代码: 构建 HTTP 请求System.out.println("X-AccessKeyId: " + accessKeyId);System.out.println("X-Timestamp: " + timestamp);System.out.println("X-Signature: " + signature);// 之后再把 requestBody 当作 JSON 发出 (POST)// ...// 这是示例演示,真实项目中可用 HttpClient、OkHttp 等发请求}/*** 计算字符串的 SHA-256 再转 hex (可选:也可用 Base64)*/private static String sha256Hex(String data) throws Exception {MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] bytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(bytes);}/*** HMAC-SHA256 + Base64*/private static String hmacSha256Base64(String data, String secret) throws Exception {Mac mac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");mac.init(keySpec);byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(rawHmac);}private static String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder(bytes.length * 2);for (byte b : bytes) {sb.append(String.format("%02x", b));}return sb.toString();}
}
  • 核心StringToSign 拼装 + HMAC-SHA256 计算签名 + 在请求头中带上 accessKeyIdtimestampsignature
  • bodyHash 的计算方式可自行定义,也可以用 MD5、直接放明文 body 等。只要客户端和服务端保持一致即可。

服务端示例 (Spring Boot)

下面以 Spring Boot + Controller 为例,展示如何验证签名。主要逻辑:

  1. 从 HTTP 头中取 X-AccessKeyId, X-Timestamp, X-Signature
  2. 根据 accessKeyId 找到 secret;
  3. 用相同的方式拼装 StringToSign
  4. 做同样的 HMAC-SHA256 计算;
  5. 比对与客户端传来的 signature 是否相同。

2.1 Controller 示例

@RestController
@RequestMapping("/api")
public class SecureApiController {// 示例:内存中保存 keyId -> keySecret 映射private Map<String, String> keyStore = new HashMap<>();public SecureApiController() {// 假设这里初始化了一个myKeyId -> myKeySecretkeyStore.put("myKeyId", "myKeySecret");}@PostMapping("/secure")public ResponseEntity<?> secureEndpoint(HttpServletRequest request,@RequestBody(required=false) String body // raw JSON) {try {// 1) 从header读取String accessKeyId = request.getHeader("X-AccessKeyId");String timestamp = request.getHeader("X-Timestamp");String clientSignature = request.getHeader("X-Signature");if (accessKeyId == null || timestamp == null || clientSignature == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing auth headers");}// 2) 查找keySecretString keySecret = keyStore.get(accessKeyId);if (keySecret == null) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid accessKeyId");}// 3) 计算 bodyHash(可选)//    假设客户端用了 sha256Hex(body)String bodyHash = sha256Hex(body == null ? "" : body);// 4) 与客户端相同的拼接方式String method = request.getMethod(); // "POST"String path = request.getRequestURI(); // "/api/secure"// StringToSignString stringToSign = method + "\n" + path + "\n" + timestamp + "\n" + bodyHash;// 5) 服务端做 HMAC-SHA256String serverSignature = hmacSha256Base64(stringToSign, keySecret);// 6) 比对签名if (!serverSignature.equals(clientSignature)) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Signature mismatch");}// 7) 可选校验: timestamp 是否过期if (!checkTimestampValid(timestamp)) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Timestamp expired or invalid");}// 8) 一切正常return ResponseEntity.ok("Success! Request body was: " + body);} catch (Exception e) {return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Auth error: " + e.getMessage());}}// 计算 SHA256Hexprivate String sha256Hex(String data) throws Exception {MessageDigest md = MessageDigest.getInstance("SHA-256");byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));return bytesToHex(digest);}// HMAC-SHA256 + Base64private String hmacSha256Base64(String data, String secret) throws Exception {Mac mac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");mac.init(keySpec);byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(rawHmac);}private String bytesToHex(byte[] bytes) {StringBuilder sb = new StringBuilder(bytes.length * 2);for (byte b : bytes) {sb.append(String.format("%02x", b));}return sb.toString();}// 时间戳校验 (±15分钟示例)private boolean checkTimestampValid(String timestampStr) {try {// 这里假设 timestampStr 是 yyyyMMddHHmmssDateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");LocalDateTime reqTime = LocalDateTime.parse(timestampStr, fmt);LocalDateTime now = LocalDateTime.now();return !reqTime.isBefore(now.minusMinutes(15)) && !reqTime.isAfter(now.plusMinutes(15));} catch (Exception e) {return false;}}
}
  • 注意:上面为了演示方便,用 @RequestBody(required=false) String body 直接拿到原始 JSON 字符串,再做 sha256Hex;如果是对象映射,你要注意读取流计算摘要的先后顺序。
  • 你也可以在 FilterInterceptor 里做这个签名验签逻辑,避免在每个 Controller 里写。
  • timestamp 校验 + 可能的 nonce 防重放(可用 Redis 记录 5 分钟内出现过的 (accessKeyId,timestamp,nonce)),以更好地防御重复调用。

总结

  1. 客户端
    • 准备 accessKeyId, accessKeySecret
    • 拼出 StringToSign(通常包含 method、path、timestamp、bodyHash 等);
    • HMAC-SHA256( StringToSign, accessKeySecret ) → signature
    • 在 HTTP 请求头里带上 accessKeyId, signature, timestamp
    • 用 JSON 作为请求体时,别忘了和服务端在 bodyHash 算法上保持一致。
  2. 服务端
    • 通过 accessKeyId 找到对应的 accessKeySecret
    • 按同样规则构造 StringToSign
    • 计算 HMAC-SHA256 并和客户端的 signature 对比;
    • 一致则通过,不一致则 401/403;
    • 可加时间戳nonce限流 等加强安全性。
  3. 优点
    • 不需要公钥/私钥,也不需要RSA 加解密;
    • 计算速度快、实现相对简单;
    • 通用性强,许多云厂商、API网关都采用类似 HMAC 签名模式。
  4. 注意
    • 一定要保护好 accessKeySecret,客户端泄露就会被冒用。
    • 使用 HTTPS 来保证传输安全,防止中间人截获签名或篡改。
    • 若对大文件、流式上传等,需要在数据处理上稍作适配(hash可能需分段计算)。

这套 HMAC-SHA256 签名鉴权就是在很多云服务(阿里云、AWS、腾讯云)都在用的模式。只要客户端与服务端约定好StringToSign 的拼装方式、accessKeyId → secret 映射、时间戳/nonce防重放,就能形成一套轻量、高效的对外接口鉴权机制。

相关文章:

Spring Boot对访问密钥加解密——HMAC-SHA256

HMAC-SHA256 简介 HMAC-SHA256 是一种基于 哈希函数 的消息认证码&#xff08;Message Authentication Code, MAC&#xff09;&#xff0c;它结合了哈希算法&#xff08;如 SHA-256&#xff09;和一个密钥&#xff0c;用于验证消息的完整性和真实性。 HMAC 是 “Hash-based M…...

HTML 元素:网页构建的基础

HTML 元素:网页构建的基础 HTML(HyperText Markup Language,超文本标记语言)是构建网页的基石。它定义了网页的结构和内容,而HTML元素则是构成HTML文档的基石。在本篇文章中,我们将深入探讨HTML元素的概念、类型、用法,以及如何在网页设计中有效地使用它们。 什么是HT…...

HEIC 是什么图片格式?如何把 iPhone 中的 HEIC 转为 JPG?

在 iPhone 拍摄照片时&#xff0c;默认的图片格式为 HEIC。虽然 HEIC 格式具有高压缩比、高画质等优点&#xff0c;但在某些设备或软件上可能存在兼容性问题。因此&#xff0c;将 HEIC 格式转换为更为通用的 JPG 格式就显得很有必要。本教程将介绍如何使用简鹿格式工厂&#xf…...

爆肝1个月:DDR4 的信号完整性(万字长文SI)

前言&#xff1a; 大学里面&#xff0c;总有很多课程&#xff0c;很浪费时间&#xff0c;学了没点用处&#xff0c;问过老师&#xff0c;为什么信号完整性&#xff0c;示波器使用等课程不开呢&#xff0c;这种是对工作真实有帮助的&#xff1f; 老师&#xff1a;因为老师…...

前端js验证码插件

相关代码,在最上方的绑定资源...

关于easy-es对时间范围查询遇到的小bug

前言&#xff1a;在使用easy-es之前作为一个小白的我只有es原生查询的基础&#xff0c;在自己通过查看官方文档自学easy-es遇到了一个挫折&#xff0c;其他的还好语法和MybatisPlus差不多&#xff0c;正以为我觉得很快就能入手&#xff0c;在对时间范围的判断就给我当头一棒&am…...

Mask R-CNN

目录 摘要 Abstract Mask R-CNN 网络架构 Backbone RPN Proposal Layer ROIAlign bbox检测 Mask分割 损失计算 实验复现 总结 摘要 Mask R-CNN是在Faster R-CNN的基础上进行改进的目标检测和实例分割网络。Faster R-CNN主要用于目标检测&#xff0c;输出对象的边…...

大模型-Dify使用笔记

大模型-Dify使用笔记 0、调整docker镜像源1、安装1、Docker Compose方式部署 2、访问 Dify 0、调整docker镜像源 由于墙的存在&#xff0c;所以默认的docker镜像源很难拉取项目&#xff0c;需要调整相关的docker配置文件 vim /etc/docker/daemon.json添加如下docker镜像源 { …...

Suno Api V4模型无水印开发「综合实战开发自己的音乐网站」 —— 「Suno Api系列」第14篇

历史文章 Suno AI API接入 - 将AI音乐接入到自己的产品中&#xff0c;支持120并发任务 Suno Api V4模型无水印开发「灵感模式」 —— 「Suno Api系列」第1篇 Suno Api V4模型无水印开发「自定义模式」 —— 「Suno Api系列」第2篇 Suno Api V4模型无水印开发「AI生成歌词」…...

云原生架构中的中间件容器化:优劣势与实践探索

在云原生架构逐步推进的过程中&#xff0c;许多企业已经开始将应用和服务容器化&#xff0c;以充分利用云计算带来的弹性和自动化。随着容器技术的发展&#xff0c;容器化不仅仅限于应用层&#xff0c;越来越多的中间件也被考虑纳入容器化范畴&#xff0c;包括Redis、Kafka、Ra…...

如何测试模型推理性能:从零开始的Python指南

如何测试模型推理性能&#xff1a;从零开始的Python指南 什么是模型推理性能&#xff1f;测试模型推理性能的步骤1. 监测内存使用情况2. 测试模型吞吐量 运行测试总结 在机器学习和深度学习中&#xff0c;模型的推理性能是一个非常重要的指标。它可以帮助我们了解模型在实际应用…...

我们来学activiti -- bpmn

bpmn 题记bpmn结余 题记 在《Activiti很难学》提到学习知识点需要面对的思想钢印问题 按常见步骤&#xff0c;先展示下官方的客套话 BPMN&#xff08;Business Process Model and Notation&#xff09;是一种业务流程建模符号&#xff0c; 它是一种图形化的语言&#xff0c;用…...

【每日学点鸿蒙知识】节点析构问题、区分手机和pad、 Navigation路由问题、Tabs组件宽度、如何监听Map

1、HarmonyOS 只调用根节点的dispose,是否其下的子节点都能析构掉还是需要遍历子节点&#xff0c;都执行dispose才能正常析构&#xff1f; 前端持有引用关系的需要dispose&#xff0c;new出来的builderNode和FrameNode也需要dispose。只调用根节点的dispose,无法保证其下的子节…...

敏捷测试文化的转变

敏捷文化是敏捷测试转型的基础&#xff0c;只有具备敏捷文化的氛围&#xff0c;对组织架构、流程和相关测试实践的调整才能起作用。在前面的敏捷测试定义中&#xff0c;敏捷测试是遵从敏捷软件开发原则的一种测试实践&#xff0c;这意味着敏捷的价值观。 此外&#xff0c;从传…...

如何配置线程池参数,才能创建性能最好、最稳定的Spring异步线程池?

配置性能最好、最稳定的Spring异步线程池&#xff0c;需要综合考虑业务场景、硬件资源&#xff08;CPU核心数、内存等&#xff09;、并发量、任务特性&#xff08;CPU密集型、IO密集型等&#xff09;以及线程池参数。 以下是优化线程池配置的关键点及代码示例&#xff1a; 线程…...

【时间之外】IT人求职和创业应知【80】-特殊日子

目录 北京冬季招聘会 OpenAI CEO炮轰马斯克 英伟达推出全新AI芯片B300 莫欢喜&#xff0c;总成空。本周必须要谨行慎言。 感谢所有打开这个页面的朋友。人生不如意&#xff0c;开越野车去撒野&#xff0c;会害了自己&#xff0c;不如提升自己。提升自己的捷径就是学习和思考…...

Vue中接入萤石等直播视频(更新中ing)

一、萤石&#xff1a; 1. 萤石云开发文档&#xff1a; https://open.ys7.com/help/31 2、安装&#xff1a; npm install ezuikit-js --save 3、在文件中引用&#xff1a;import EZUIKit from ezuikit-js 4、具体代码&#xff1a; 获取accessToken&#xff1a;https://open.…...

如何学习、使用Ai,才能跟上时代的步伐?

目录 1. 打好基础&#xff1a;理解AI的核心概念 2. 学习AI的核心领域 3. 实践&#xff1a;动手做项目&#xff0c;积累经验 4. 利用AI工具提升工作效率 5. 培养AI思维与批判性思维 6. 关注AI领域的最新研究与趋势 7. 培养跨学科能力 总结&#xff1a; 在AI时代&#xf…...

RabbitMQ中的异步Confirm模式:提升消息可靠性的利器

在现代分布式系统中&#xff0c;消息队列&#xff08;Message Queue&#xff09;扮演着至关重要的角色&#xff0c;它能够解耦系统组件、提高系统的可扩展性和可靠性。RabbitMQ作为一款广泛使用的消息队列中间件&#xff0c;提供了多种机制来确保消息的可靠传递。其中&#xff…...

Linux(Centos 7.6)目录结构详解

Linux(Centos 7.6)是一个操作系统&#xff0c;其核心设计理念是将一切资源抽象为文件&#xff0c;即一切皆文件。比如系统中的硬件设备硬盘、网络接口等都被视为文件。Windows系统一般是分为C、D、E盘。而Linux(Centos 7.6)是以斜线"/"作为文件系统的开始目录&#x…...

idea大量爆红问题解决

问题描述 在学习和工作中&#xff0c;idea是程序员不可缺少的一个工具&#xff0c;但是突然在有些时候就会出现大量爆红的问题&#xff0c;发现无法跳转&#xff0c;无论是关机重启或者是替换root都无法解决 就是如上所展示的问题&#xff0c;但是程序依然可以启动。 问题解决…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

GitFlow 工作模式(详解)

今天再学项目的过程中遇到使用gitflow模式管理代码&#xff0c;因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存&#xff0c;无论是github还是gittee&#xff0c;都是一种基于git去保存代码的形式&#xff0c;这样保存代码…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...